Полиморфная переменная-член класса - PullRequest
13 голосов
/ 23 мая 2011

У меня есть класс 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 является полиморфным базовым классом, это больше не работает (нарезка).

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

  1. Я не хочу передавать указатель на конструктор, и
  2. Класс printer не должен нуждаться в виртуальном методе clone (= должен полагаться на конструкцию копирования).

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

В C ++ 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? И есть ли способ избавиться от совершенно ненужной копии в конструкторе? К сожалению, перемещение временного здесь не работает (верно?).

Ответы [ 6 ]

9 голосов
/ 23 мая 2011

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

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

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

Редактировать: Хорошо, еще один способ:

  • Сделайте сам printer умным указателем на фактическую реализацию. Он может быть копируемым и полиморфным одновременно за счет некоторой сложности.
2 голосов
/ 23 мая 2011
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;
};

Та же концепция будет применяться в C ++ 03, но замена unique_ptr для auto_ptr и канавы ссылочный Rvalue перегрузки.

Кроме того, вы могли бы подумать о каком-то "фиктивном" конструкторе для C ++ 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 использует для «перемещения» в C ++ 03.Конечно, следует использовать с осторожностью, но совершенно законно и выполнимо.Проблема в том, что вы влияете на все printer подклассы.

2 голосов
/ 23 мая 2011

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

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

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

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

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

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

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

1 голос
/ 24 мая 2011

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

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

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 имеет свои обязанности, и стирание типов не является одним из них, его можно делегировать.

1 голос
/ 23 мая 2011

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

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

private:
    printer& pp;
};

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

0 голосов
/ 23 мая 2011

Как насчет шаблонизации 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.Я не буду описывать в деталях, но я хотел бы, чтобы суть была более ясной.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...