Полиморфная переменная-член класса

У меня есть класс messenger, который опирается на экземпляр printer. printer - это полиморфный базовый класс, и фактический объект передается messenger в конструкторе.

Для неполиморфного объекта я бы просто сделал следующее:

class messenger {
public:
    messenger(printer const& pp) : pp(pp) { }

    void signal(std::string const& msg) {
        pp.write(msg);
    }

private:
    printer pp;
};

Но когда printer является полиморфным базовым классом, это больше не работает (нарезка).

Каков наилучший способ сделать эту работу, учитывая, что

  • Я не хочу передавать указатель на конструктор и
  • Класс printer не должен иметь виртуальный метод clone (= должен полагаться на построение копии).

Я не хочу передавать указатель на конструктор, потому что остальная часть API работает с реальными объектами, а не с указателями, и было бы непонятно, что здесь есть указатель в качестве аргумента.

В С++ 0x я мог бы использовать unique_ptr вместе с конструктором шаблонов:

struct printer {
    virtual void write(std::string const&) const = 0;
    virtual ~printer() { } // Not actually necessary …
};

struct console_printer : public printer {
    void write(std::string const& msg) const {
        std::cout << msg << std::endl;
    }
};

class messenger {
public:
    template <typename TPrinter>
    messenger(TPrinter const& pp) : pp(new TPrinter(pp)) { }

    void signal(std::string const& msg) {
        pp->write(msg);
    }

private:
    std::unique_ptr<printer> pp;
};

int main() {
    messenger m((console_printer())); // Extra parens to prevent MVP.

    m.signal("Hello");
}

Это лучшая альтернатива? Если да, то какой был бы лучший способ в pre-0x? И есть ли способ избавиться от совершенно ненужной копии в конструкторе? К сожалению, перемещение временного не работает здесь (правильно?).

Ответы

Ответ 1

Невозможно клонировать полиморфный объект без метода виртуального клонирования. Таким образом, вы можете:

  • передайте и удерживайте ссылку и убедитесь, что принтер не был уничтожен перед мессенджером в кодовом конструировании мессенджера,
  • передать и удерживать интеллектуальный указатель и создать экземпляр принтера с новым,
  • передать ссылку и создать экземпляр экземпляра в куче, используя метод clone или
  • передать ссылку на фактический тип в шаблон и создать экземпляр с новым, пока вы все еще знаете тип.

Последнее, что вы предлагаете с С++ 0x std::unique_ptr, но в этом случае С++ 03 std::auto_ptr сделает вам точно такую ​​же службу (т.е. вам не нужно ее перемещать, иначе они то же).

Изменить: Ok, um, еще один способ:

  • Сделать printer сам умным указателем на фактическую реализацию. Затем он копируется и полиморфно в то же время за счет некоторой сложности.

Ответ 2

Unfortunately, moving the temporary doesn’t work here (right?).

Неправильно. Быть, глупо. Это то, на что ссылаются ссылки rvalue. Простая перегрузка быстро решит проблему.

class messenger {
public:
    template <typename TPrinter>
    messenger(TPrinter const& pp) : pp(new TPrinter(pp)) { }
    template <typename TPrinter>
    messenger(TPrinter&& pp) : pp(new TPrinter(std::move(pp))) { }

    void signal(std::string const& msg) {
        pp->write(msg);
    }

private:
    std::unique_ptr<printer> pp;
};

Та же концепция будет применяться в С++ 03, но замените unique_ptr на auto_ptr и отбросьте опорную перегрузку rvalue.

Кроме того, вы можете рассмотреть какой-то конструктор "dummy" для С++ 03, если вы в порядке с небольшим изворотливым интерфейсом.

class messenger {
public:
    template <typename TPrinter>
    messenger(TPrinter const& pp) : pp(new TPrinter(pp)) { }
    template<typename TPrinter> messenger(const TPrinter& ref, int dummy) 
        : pp(new TPrinter()) 
    {
    }
    void signal(std::string const& msg) {
        pp->write(msg);
    }

private:
    std::unique_ptr<printer> pp;
};

Или вы могли бы рассмотреть ту же стратегию, что auto_ptr использует для "перемещения" в С++ 03. Для использования с осторожностью, конечно, но совершенно законным и выполнимым. Проблема в том, что вы влияете на все подклассы printer.

Ответ 3

Расширение комментария в правильный ответ...

Первоочередной задачей здесь является право собственности. Из вашего кода кажется, что каждый экземпляр messenger имеет собственный экземпляр принтера, но на самом деле вы передаете готовый принтер (предположительно с некоторым дополнительным состоянием), который вам нужно затем скопировать в свой собственный экземпляр of printer. Учитывая предполагаемый характер объекта printer (т.е. Для печати чего-либо), я бы сказал, что вещью, к которой он относится, является общий ресурс - в этом свете нет смысла для каждого экземпляра messenger иметь его собственная копия printer (например, что, если вам нужно заблокировать доступ к std::cout)?

С точки зрения дизайна, то, что messenger требуется для построения, на самом деле действительно является указателем на какой-то общий ресурс - в этом свете лучше shared_ptr (лучше еще weak_ptr).

Теперь, если вы не хотите использовать weak_ptr, и вы предпочитаете хранить ссылку, подумайте о том, можете ли вы messenger подключиться к типу printer, связь остается у пользователя, вам все равно - конечно, основным недостатком этого является то, что messenger не будет сдерживаемым. ПРИМЕЧАНИЕ. Вы можете указать класс признаков (или политики), на который может быть введен тип messenger, и это предоставляет информацию о типе для принтера (и может управляться пользователем).

Третья альтернатива заключается в том, что у вас есть полный контроль над набором принтеров, и в этом случае удерживайте вариант типа - он намного чище IMHO и избегает полиморфизма.

Наконец, если вы не можете подключиться, вы не можете управлять принтерами и хотите, чтобы ваш собственный экземпляр printer (того же типа), шаблон конструктора преобразования - это путь вперед, однако добавьте disable_if, чтобы предотвратить это называется неправильным (т.е. как нормальная копия ctor).

В целом я рассматривал бы принтер как общий ресурс и держал бы weak_ptr, так как, честно говоря, он позволяет лучше контролировать этот общий ресурс.

Ответ 4

Почему вы не хотите передавать указатель или интеллектуальный указатель?

В любом случае, если вы всегда инициализируете член принтера в конструкторе, вы можете просто использовать ссылочный элемент.

private:
    printer& pp;
};

И инициализируется в списке инициализации конструктора.

Ответ 5

Когда у вас есть золотой молот, все выглядит как гвозди.

Ну, мой последний золотой молот - стирание стилей. Серьезно, я бы не использовал его, но опять же, я бы передал указатель и создал вызывающий и добавлял зависимость.

struct printer_iface {
   virtual void print( text const & ) = 0;
};

class printer_erasure {
   std::shared_ptr<printer_iface> printer;
public:
   template <typename PrinterT>
   printer_erasure( PrinterT p ) : printer( new PrinterT(p) ) {}

   void print( text const & t ) {
      printer->print( t );
   }
};

class messenger {
   printer_erasure printer;
public:
   messenger( printer_erasure p ) : printer(p) {}
...
};

Хорошо, возможно, это и решения, предоставляемые с шаблоном, - это то же самое, с единственной небольшой разницей, что сложность стирания типа перемещается за пределы класса. Класс messenger имеет свои собственные обязанности, и стирание типа не является одним из них, оно может быть делегировано.

Ответ 6

Как насчет шаблонов class messanger?

template <typename TPrinter>
class messenger {
public:
    messenger(TPrinter const& obj) : pp(obj) { }
    static void signal(printer &pp, std::string const& msg) //<-- static
    {
        pp->write(msg);
    }
private:
    TPrinter pp;  // data type should be template
};

Обратите внимание, что signal() сделано static. Это должно использовать способность virtual class printer и не создавать новую копию signal(). Единственное, что вам нужно сделать, - вызвать функцию, например,

signal(this->pp, "abc");

Предположим, что у вас есть другие типы данных, а затем pp, которые не связаны с типом шаблона, тогда их можно перенести в базовый класс без шаблона, и эта база может быть унаследована messenger. Я не описываю много деталей, но, я хочу, чтобы точка была более ясной.