C виртуальный конструктор копирования

Обновлено: 29.04.2024

Can we make a class constructor virtual in C++ to create polymorphic objects? No. C++ being statically typed (the purpose of RTTI is different) language, it is meaningless to the C++ compiler to create an object polymorphically. The compiler must be aware of the class type to create the object. In other words, what type of object to be created is a compile-time decision from the C++ compiler perspective. If we make constructor virtual, compiler flags an error. In fact, except inline, no other keyword is allowed in the declaration of the constructor.

In practical scenarios, we would need to create a derived class object in a class hierarchy based on some input. Putting in other words, object creation and object type are tightly coupled which forces modifications to extended. The objective of the virtual constructor is to decouple object creation from its type.

How can we create the required type of an object at runtime? For example, see the following sample program.

In the above sample, assume that the hierarchy Base, Derived1 and Derived2 are part of library code. The class User is utility class trying to make use of the hierarchy. The main function is consuming Base hierarchy functionality via User class.

The User class constructor is creating Derived1 object, always. If the User‘s consumer (the main in our case) needs Derived2 functionality, User needs to create “new Derived2()” and it forces recompilation. Recompiling is bad way of design, so we can opt for the following approach.

Before going into details, let us answer, who will dictate to create either of Derived1 or Derived2 object? Clearly, it is the consumer of User class. The User class can make use of if-else ladder to create either Derived1 or Derived2, as shown in the following sample,

The above code is *not* open for extension, an inflexible design. In simple words, if the library updates the Base class hierarchy with new class Derived3. How can the User class creates Derived3 object? One way is to update the if-else ladder that creates Derived3 object based on new input ID 3 as shown below,

The above modification forces the users of User class to recompile, bad (inflexible) design! And won’t close User class from further modifications due to Base extension.

The problem is with the creation of objects. Addition of new class to the hierarchy forcing dependents of User class to recompile. Can’t we delegate the action of creating objects to class hierarchy itself or to a function that behaves virtually? By delegating the object creation to class hierarchy (or to a static function) we can avoid the tight coupling between User and Base hierarchy. Enough theory, see the following code,

The User class is independent of object creation. It delegates that responsibility to Base, and provides an input in the form of ID. If the library adds new class Derived4, the library modifier will extend the if-else ladder inside Create to return proper object. Consumers of User need not recompile their code due to extension of Base.

Note that the function Create used to return different types of Base class objects at runtime. It acts like virtual constructor, also referred as Factory Method in pattern terminology.

The pattern world demonstrates different ways to implement the above concept. Also, there are some potential design issues with the above code. Our objective is to provide some insights into virtual construction, creating objects dynamically based on some input. We have excellent books devoted to the subject, the interested reader can refer them for more information.

— Venki. Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.

Can we make a class copy constructor virtual in C++? How to use?

@DavidRodríguez-dribeas: That is a good point. I think it should be an answer, as it also explains the rationale

@DavidRodríguez-dribeas: the c++faq link in Luchian's answer provides an answer. I wouldn't mind having it directly in the language instead of having to provide create() and clone() (say in the next standard).

@stefaanv, nawaz: I know the idiom, the comment is meant to make you think that the constructor is applied to an object that is not yet created (at this point it is only allocated memory), and that dispatch in C++ is applied on the object of which the method is being called (at this point just a memory block). The idiom reverses the order, and uses virtual dispatch on the source object, rather than the destination, which is a valid object. The intention was making the user think on what was being asked. As of the idiom becoming part of the standard, I would not bet on it.

6 Answers 6

No you can't, constructors can't be virtual.

C++03 - 12.1 Constructors

4) A constructor shall not be virtual (10.3) or static (9.4). [. ]

If you need something like this, you can look up the virtual constructor idiom here.

Although solving it with virtual methods can be in most cases okay, it is important to mention, that these are not constructors, it is an abstract factory.

Furthermore, the whole concept does not make sense. Virtual functions are functions that are dispatched based on the value of an object (the dynamic type of the object). When a constructor is called, the object does not yet have a value (because it has not yet been constructed). Therefore, no virtual dispatch can possibly occur.

Think about it. What semantics would such a constructor have?

No. C++ being static typed language, it is meaningless to the C++ compiler to create an object polymorphically. The compiler must be aware of the class type to create the object. In other words, what type of object to be created is a compile time decision from C++ compiler perspective. If we make constructor virtual, compiler flags an error.

You cannot because the memory is allocated before the constructor is called based on the size of the new type not the copy operand. And if it did work it would be a special case that inverted polymorphism for a number of language constructs.

But that doesn't mean it can't be done with a little C++ magic. :)

There are couple cases where it is incredibly helpful, Serializing non-POD classes for instance. This example creates a virtual copy constructor that works using placement new.

Warning: This is an example that may help some users with specific problems. Do not do this in general purpose code. It will crash if the memory allocated for the new class is smaller than the derived class. The best (and only) safe way to use this is if you are managing your own class memory and using placement new.

This won't compile, even if you fix it to get it to compile, the implementation is rather thoroughly broken. All members of Derived will suffer from double initialization. This might happen to work with some simple types, but clearly results in undefined behavior. The "constructor" would need to be void VirtualPlacementCopyConstructor(VirtualBase* place) const < auto d = dynamic_cast(place); if (d) < d->~Derived(); new (d) Derived(*this); > >

It should be now obvious what the problem is: the VirtualPlacementCopyConstructor is invoked way too early, when the type of the copied-into class is not Derived yet. Thus the destructor won't do the right thing either, and you get undefined behavior aplenty. If you replace dynamic_cast with static_cast , things will work for POD types, and that's about it. As soon as you add e.g. a std::string member, stuff will break (worse yet: it won't always break, so you'll be pulling your hair out).

Never, it won't possible in C++.

Yes you can create virtual copy constructor but you can not create virtual constructor.

Virtual Constructor:- Not Possible because c++ is static type language and create constructor as a virtual so compiler won't be able to decide what type of object it and leave the whole process for run time because of virtual keyword. The compiler must be aware of the class type to create the object. In other words, what type of object to be created is a compile time decision from C++ compiler perspective. If we make constructor virtual, compiler flags an error.

Virtual Copy constructor:- Yes Possible, consider clip board application. A clip board can hold different type of objects, and copy objects from existing objects, pastes them on application canvas. Again, what type of object to be copied is a runtime decision. Virtual copy constructor fills the gap here.

I'm reading C++ Design Patterns and Derivatives Pricing by Mark Joshi and implementing his code in C++11. Everything has gone pretty well until I hit chapter 4 where he discusses virtual copy constructors.

The problem here is that VanillaOption contains a reference to thePayOff . If that's the case and someone modifies thePayOff , the the behavior of theOption could be modified unwittingly. The solution he advises is to create a virtual copy constructor in PayOffDoubleDigital 's base class, PayOff so that theOption contains its own copy:

and then defined in each inherited class:

Returning new caught me as something that might be inappropriate in C++11. So what is the proper way of handling this using C++11?

2 Answers 2

The solution he advises is to create a virtual copy constructor in PayOffDoubleDigital's base class [. ]

Function clone() is just a regular (virtual) function, and its particular meaning is recognized by you - the user - as something which creates clones of your object, but not by the compiler, which has no idea what clone() does.

Returning new caught me as something that might be inappropriate in C++11

That's true, using new is not idiomatic in C++11. In fact, in C++11 you should (almost) never use new unless you are doing really low-level memory management (something you should avoid unless you really have to) - and in C++14, you can remove the "almost". Unfortunately, this is likely the exceptional case where new is needed.

I'm saying this because I believe returning a unique_ptr sounds like the appropriate thing to do here (the option object has to hold its own PayOff object, and that must stay alive just as long as the option object is alive), and there is no std::make_unique() function in C++11 (it will be there in C++14):

Having VanillaOption (or its base class) hold a unique_ptr rather than a raw pointer would make it unnecessary to delete the PayOff object returned by clone() . In turn, not having to delete that object means no need to define a user-provided destructor, and no need to take care about the Rule of Three, Rule of Five, or whatnot.

Все мы знаем, что в C++ нет такого понятия как виртуальный конструктор, который бы собирал нужный нам объект в зависимости от каких-либо входных параметров на этапе выполнения. Обычно для этих целей используется параметризованный фабричный метод (Factory Method). Однако мы можем сделать «ход конем» и сымитировать поведение виртуального конструктора с помощью методики, называемой «конверт и письмо» («Letter/Envelope»).

Не помню, где я об этом узнал, но, если я не ошибаюсь, такую технику предложил Джим Коплиен (aka James O. Coplien) в книге «Advanced C++ Programming Styles and Idioms».

Идея состоит в том, чтобы внутри базового класса (Конверта) хранить указатель на объект этого же типа (Письма). При этом Конверт должен «перенаправлять» вызовы виртуальных методов на Письмо. С хорошими примерами у меня, как всегда, небольшие проблемки, поэтому «промоделируем» систему магических техник (или заклинаний) =) Предположим, что для каждой техники используется один из пяти основных элементов (а может и их комбинация), от которых зависит воздействие этой техники на окружающий мир и на предмет, к которому она применяется. В то же время мы хотим иметь возможность работать со всеми техниками независимо от их типа.

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

using std :: cout ;
using std :: endl ;

enum
<
FIRE = 0x01 ,
WIND = 0x02 ,
LIGHTNING = 0x04 ,
SOIL = 0x08 ,
WATER = 0x10
> ;

class Skill // aka Jutsu =)
<
public :
// virtual (envelope) constructor (see below)
Skill ( int _type ) throw ( std :: logic_error ) ;

// destructor
virtual ~Skill ( )
<
if ( mLetter )
<
// virtual call in destructor!
erase ( ) ;
>

delete mLetter ; // delete Letter for Envelope
// delete 0 for Letter
>

virtual void cast ( ) const < mLetter - >cast ( ) ; >
virtual void show ( ) const < mLetter - >show ( ) ; >
virtual void erase ( ) < mLetter - >erase ( ) ; >

protected :
// letter constructor
Skill ( ) : mLetter ( NULL )

private :
Skill ( const Skill & ) ;
Skill & operator = ( Skill & ) ;

Skill * mLetter ; // pointer to letter
> ;


class FireSkill : public Skill
<
public :
~FireSkill ( ) < cout
virtual void cast ( ) const < cout
virtual void show ( ) const < cout
virtual void erase ( ) < cout
private :
friend class Skill ;
FireSkill ( ) < >
FireSkill ( const FireSkill & ) ;
FireSkill & operator = ( FireSkill & ) ;
> ;


class WoodSkill : public Skill
<
public :
~WoodSkill ( ) < cout
virtual void cast ( ) const < cout
virtual void show ( ) const < cout
virtual void erase ( ) < cout
private :
friend class Skill ;
WoodSkill ( ) < >
WoodSkill ( const WoodSkill & ) ;
WoodSkill & operator = ( WoodSkill & ) ;
> ;


Skill :: Skill ( int _type ) throw ( std :: logic_error )
<
switch ( _type )
<
case FIRE :
mLetter = new FireSkill ;
break ;

case SOIL | WATER :
mLetter = new WoodSkill ;
break ;

default :
throw std :: logic_error ( "Incorrect type of element" ) ;
>

// virtual call in constructor!
cast ( ) ;
>


int main ( )
<
std :: vector < Skill * >skills ;

try
<
skills. push_back ( new Skill ( FIRE ) ) ;
skills. push_back ( new Skill ( SOIL | WATER ) ) ;
// skills.push_back(new Skill(LIGHTNING));
>
catch ( std :: logic_error le )
<
std :: cerr return EXIT_FAILURE ;
>

for ( size_t i = 0 ; i < skills. size ( ) ; i ++ )
<
skills [ i ] - > show ( ) ;
delete skills [ i ] ;
>

В принципе это не так интересно, но вывод будет следующим:

Katon!
Mokuton!
FireSkill::show()
FireSkill:erase()
~FireSkill()
WoodSkill::show()
WoodSkill::erase()
~WoodSkill()

Давайте лучше разберёмся, что же происходит.

Итак, у нас есть класс Skill (конверт), содержащий указатель на объект такого же типа (письмо). Конструктор копирования и оператор присваивания скроем в private от греха подальше. Основной интерес представляют два конструктора класса, один из которых открытый, а другой защищенный, а также деструктор.

Открытый конструктор, он же конструктор Конверта, он же в нашем случае «виртуальный конструктор» (его определение находится ниже), принимает один параметр — тип «элемента», на основе которого будет вычислен тип конструируемого объекта. В зависимости от входного параметра указатель на письмо инициализируется указателем на конкретный объект (FireSkill, WoodSkill и т.п., которые унаследованы от Skill). В случае, если во входном параметре неверное значение, выбрасывается исключение.

В производных классах техник FireSkill, WoodSkill и т.д. конструкторы по умолчанию закрыты, но базовый класс Skill объявлен как friend, что позволяет создавать объекты этих классов только внутри класса Skill. Конструктор копии и оператор присваивания в этих классах закрыты и не определены. Все виртуальные методы класса Skill переопределены в производных.

Виртуальный конструктор должен быть определен ниже всех производных классов, чтобы не пришлось париться с опережающими объявлениями (forward declarations), так как внутри него создаются объекты производных классов.

Когда в виртуальном конструкторе создаются объекты производных классов, при конструировании этих объектов сначала должен вызываться конструктор по умолчанию базового класса. Дефолтный конструктор базового класса ничего не делает, кроме инициализации нулем указателя на письмо. Фактически этот конструктор — конструктор письма, что мы и указываем, записывая в mLetter нуль.

Каким образом происходит вызов виртуальных методов? В базовом классе внутри виртуальных методов идет «перенаправление»: фактически Конверт играет роль оболочки, которая просто вызывает методы Письма. Так как методы Письма вызываются через указатель, то происходит позднее связывание, то есть вызов будет виртуальным. Более того! Мы можем виртуально вызывать методы в конструкторе и деструкторе: при создании объекта Skill (Конверта) происходит вызов параметризованного конструктора этого класса, который конструирует Письмо и инициализирует mLetter. После этого мы вызываем cast(), внутри которого стоит вызов mLetter->cast(). Так как mLetter на этот момент уже инициализирован, происходит виртуальный вызов.

То же самое в деструкторе ~Skill(). Сначала мы проверяем, проинициализирован ли mLetter. Если да, значит мы находимся в деструкторе Конверта, поэтому виртуально вызываем метод зачистки Конверта, а затем его удаляем. Если же нет, значит, мы в деструкторе Конверта, в котором выполняется delete 0 (а эта конструкция вполне безопасна).

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

В данной статье я хочу затронуть вопрос виртуальности конструкторов, деструкторов, а также специфичные вопросы, так или иначе связанные с виртуальностью функций.

Статья расчитана на программистов средней и высокой квалификации. Приятного чтения.

Виртуальные конструкторы в C++

Итак, пожалуй начнем с конструкторов. Тут все очень просто — виртуальных конструкторов (а также похожих на них конструкторов) в C++ не существует. Просто потому что не бывает и всё тут (конкретно: это запрещено стандартом языка).

Вы, наверно, спросите: «а зачем такое вообще может понадобится?». На самом деле разговор про «виртуальный конструктор» немного некорректны. Конструктор не может быть виртуальным в смысле виртуальных функций, т.к. чтобы была виртуальность необходимо в конструкторе (а больше и негде особо) настроить указатель на ТВМ (таблицу виртуальных функций) для создаваемого объекта.

Замечание: обычно виртуальность реализуется через ТВМ и указатель на нее в объекте. Подробнее вы можете прочесть об этом тут

Так вот, иногда «виртуальным конструктором» называют механизм создания объект любого заранее неизвестного класса. Это может пригодится, например, при копировании массива объектов, унаследованных от общего предка (при этом нам бы очень хотелось чтобы вызывался конструктор копирования именно нужного нам класса, конечно же). В C++ для подобного, обычно, используют виртуальную функцию вроде virtual void assign (const object &o) , или подобную, однако, это не является повсеместным, и возможны другие реализации.

Виртуальный деструктор

А вот деструктор, напротив может быть виртуальным. И даже более того — это часто встречается.
Обычным является использование вирт деструктора в классах, имеющих вирт функции. Более того, gcc, например, выдаст вам предупреждение, если вы не сделаете виртуальным деструктор, объявив виртуальную функцию.

Часто можно встретить миф: «вирт деструктор нужен лишь в том случае, когда на деструктор классов-потомков возлагаются какие-то нестандартные функции, если деструктор потомка не отличается по функционалу от родителя, то делать его виртуальным нет особого смысла». Это может и будет работать «сейчас», но может сыграть злую шутку в будущем, да и в общем-то не очень верно. Если деструктор не виртуальный, то будет вызван деструктор того типа, какой заявлен в указателе. В тоже время будет правильнее что для объектов потомков должны вызываться свои деструкторы. Просто стоит принять это как правило, иначе в будущем могут быть очень большие проблемы с отладкой непонятно почему текучих в плане памяти программ.

Другой миф: чисто виртуальных деструкторов не бывает. Ещё как бывают.

* This source code was highlighted with Source Code Highlighter .


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

class Sample public :
virtual ~Sample()=0<> //обратите особое внимание сюда, так писать по стандарту нельзя, но MS VC проглотит
>;

class DSample: public Sample

* This source code was highlighted with Source Code Highlighter .

Для вменяемых компиляторов класс Sample нужно писать так:

Sample::~Sample() >

* This source code was highlighted with Source Code Highlighter .

Сразу же замечание про нормальность компилятора, и заодно миф: по стандарту определять чисто вирт. функцию внутри определения класса нельзя. Но определенные корпорации говорят «если мы видим возможность улучшить стандарт, то мы незадумываясь делаем это».

Вы ниразу не видели чисто виртуальных деструкторов с определенными «телами»? Так вот, миф что они несуществуют, также неверен. Также определять можно и другие чисто виртуальные функции.

Почему надо писать именно с определением деструктора? Ответ на самом деле прост: из налсденика DSample в его деструкторе ~DSample будет вызываться деструктор ~Sample, и поэтому его необходимо определить, иначе у вас это даже не будет компилироваться.

Для чего нужен подобный чисто виртуальный деструктор? Это используется для того, чтобы сделать класс абстрактным, не создавая чисто виртуальных функций. Другого применения я найти не смог.

Замечания об устройстве указаталей на функцию-член

Казалось бы данная часть не имеет отношения к виртуальности. Если вы так думаете, то сильно заблуждаетесь. Вообще говоря указатели на функцию член в C++ используются не так часто. Отчасти это связано с мутными (как мне кажется) главами в стандарте языка, отчасти, потому что их реализация выливается в полный кошмар для программистов компиляторов. Мне не известно ни одного компилятора, который мог бы работать с этими «штуками» полностью по стандарту.

Заключение

Вот, наверно, и все что я хотел бы рассказать вам в дополнение. Конечно, часть вопросов, связанных с виртуальностью все еще требуют открытий, оставляю это на вашей совести 8)

Удачи в программировании.

P.S. перенести бы в CPP блог надо бы… думаю там оно будет более востребовано.

Читайте также: