______________________________________________________________________ 12 Special member functions [special] ______________________________________________________________________ 1 Some member functions are special in that they affect the way objects of a class are created, copied, and destroyed, and how values may be converted to values of other types. Often such special functions are called implicitly. Also, the compiler may generate instances of these functions when the programmer does not supply them. Compiler- generated special functions may be referred to in the same ways that programmer-written functions are. 2 These member functions obey the usual access rules (_class.access_). For example, declaring a constructor protected ensures that only derived classes and friends can create objects using it. 12.1 Constructors [class.ctor] 1 A member function with the same name as its class is called a con structor; it is used to construct values of its class type. An object of class type will be initialized before any use is made of the object; see _class.init_. 2 A constructor can be invoked for a const or volatile object.1) A con structor may not be declared const or volatile (_class.this_). A con structor may not be virtual. A constructor may not be static. 3 Constructors are not inherited. Default constructors and copy con structors, however, are generated (by the compiler) where needed (_class.copy_). Generated constructors are public. 4 A default constructor for a class X is a constructor of class X that can be called without an argument. If no constructor has been declared for class X, a default constructor is implicitly declared. The definition for an implicitly-declared default constructor is gen erated only if that constructor is called. An implicitly-declared default constructor is non-trivial if and only if either the class has direct virtual bases or virtual functions or if the class has direct bases or members of a class (or array thereof) requiring non-trivial initialization (_class.init_). 5 A copy constructor for a class X is a constructor whose first parame ter is of type X& or const X& and whose other parameters, if any, all have defaults, so that it can be called with a single argument of type _________________________ 1) Volatile semantics might or might not be used. X. For example, X::X(const X&) and X::X(X&,int=0) are copy construc tors. If no copy constructor is declared in the class definition, a copy constructor is implicitly declared2). The definition for an implicitly-declared copy constructor is generated only if that copy constructor is called. +------- BEGIN BOX 1 -------+ Do we need a definition for _trivial_ implicitly-declared copy con structors? +------- END BOX 1 -------+ 6 A constructor for a class X whose first parameter is of type X or const X (not reference types), is not a copy constructor, and must have other parameters. For example, X::X(X) is ill-formed. 7 Constructors for array elements are called in order of increasing addresses (_dcl.array_). 8 If a class has base classes or member objects with constructors, their constructors are called before the constructor for the derived class. The constructors for base classes are called first. See _class.base.init_ for an explanation of how arguments can be specified for such constructors and how the order of constructor calls is deter mined. 9 An object of a class with a constructor cannot be a member of a union. 10No return type (not even void) can be specified for a constructor. A return statement in the body of a constructor may not specify a return value. It is not possible to take the address of a constructor. 11A constructor can be used explicitly to create new objects of its type, using the syntax class-name ( expression-listopt ) For example, complex zz = complex(1,2.3); cprint( complex(7.8,1.2) ); An object created in this way is unnamed (unless the constructor was used as an initializer for a named variable as for zz above), with its lifetime limited to the expression in which it is created; see _class.temporary_. _________________________ 2) Thus the class definition struct X { X(const X&, int); }; causes a copy constructor to be generated and the member function def inition X::X(const X& x, int i =0) { ... } is ill-formed because of ambiguity. 12Member functions may be called from within a constructor; see _class.cdtor_. 12.2 Temporary objects [class.temporary] 1 In some circumstances it may be necessary or convenient for the com piler to generate a temporary object. Precisely when such temporaries are introduced is implementation dependent. When a compiler intro duces a temporary object of a class that has a constructor it must ensure that a constructor is called for the temporary object. Simi larly, the destructor must be called for a temporary object of a class where a destructor is declared. For example, class X { // ... public: // ... X(int); X(const X&); ~X(); }; X f(X); void g() { X a(1); X b = f(X(2)); a = f(a); } Here, one might use a temporary in which to construct X(2) before passing it to f() by X(X&); alternatively, X(2) might be constructed in the space used to hold the argument for the first call of f(). Also, a temporary might be used to hold the result of f(X(2)) before copying it to b by X(X&); alternatively, f()'s result might be con structed in b. On the other hand, for many functions f(), the expres sion a=f(a) requires a temporary for either the argument a or the result of f(a) to avoid undesired aliasing of a. Even if the copy constructor is not called, all the semantic restrictions, such as accessibility, must be satisfied. 2 The compiler must ensure that every temporary object is destroyed. Ordinarily, temporary objects are destroyed as the last step in evalu ating the full-expression (_intro.execution_) that (lexically) con tains the point where they were created. This is true even if that evaluation ends in throwing an exception. 3 The only context in which temporaries are destroyed at a different point is when an expression appears as a declarator initializer. In that context, the temporary that holds the result of the expression must persist at least until the initialization implied by the declara tor is complete. If the declarator declares a reference, all tempo raries in the expression persist until the end of the scope in which the reference is declared. Otherwise, the declarator defines an object that is initialized from a copy of the temporary; during this copying, an implementation may call the copy constructor many times; the temporary is destroyed as soon as it has been copied. In all cases, temporaries are destroyed in reverse order of creation. Another form of temporaries is discussed in _dcl.init.ref_. 12.3 Conversions [class.conv] 1 Type conversions of class objects can be specified by constructors and by conversion functions. 2 Such conversions, often called user-defined conversions, are used implicitly in addition to standard conversions (_conv_). For example, a function expecting an argument of type X can be called not only with an argument of type X but also with an argument of type T where a con version from T to X exists. User-defined conversions are used simi larly for conversion of initializers (_dcl.init_), function arguments (_expr.call_, _dcl.fct_), function return values (_stmt.return_, _dcl.fct_), expression operands (_expr_), expressions controlling iteration and selection statements (_stmt.select_, _stmt.iter_), and explicit type conversions (_expr.type.conv_, _expr.cast_). 3 User-defined conversions are applied only where they are unambiguous (_class.ambig_, _class.conv.fct_). Conversions obey the access con trol rules (_class.access_). As ever access control is applied after ambiguity resolution (_class.scope_). 4 See _over.match_ for a discussion of the use of conversions in func tion calls as well as examples below. 12.3.1 Conversion by constructor [class.conv.ctor] 1 A constructor with a single parameter specifies a conversion from its parameter type to the type of its class. For example, class X { // ... public: X(int); X(const char*, int =0); }; void f(X arg) { X a = 1; // a = X(1) X b = "Jessie"; // b = X("Jessie",0) a = 2; // a = X(2) f(3); // f(X(3)) } When no constructor for class X accepts the given type, no attempt is made to find other constructors or conversion functions to convert the assigned value into a type acceptable to a constructor for class X. For example, class X { /* ... */ X(int); }; class Y { /* ... */ Y(X); }; Y a = 1; // illegal: Y(X(1)) not tried 12.3.2 Conversion functions [class.conv.fct] 1 A member function of a class X with a name of the form conversion-function-id: operator conversion-type-id conversion-type-id: type-specifier-seq conversion-declaratoropt conversion-declarator: ptr-operator conversion-declaratoropt specifies a conversion from X to the type specified by the conversion- type-id. Such member functions are called conversion functions. Classes, enumerations, and typedef-names may not be declared in the type-specifier-seq. Neither parameter types nor return type may be specified. A conversion operator is never used to convert a (possibly qualified) object (or reference to an object) to the (possibly quali fied) same object type (or a reference to it), or to a (possibly qual ified) base class of that type (or a reference to it). If conversion- type-id is void or cv-qualified void, the program is ill-formed. 2 Here is an example: class X { // ... public: operator int(); }; void f(X a) { int i = int(a); i = (int)a; i = a; } In all three cases the value assigned will be converted by X::operator int(). User-defined conversions are not restricted to use in assignments and initializations. For example, void g(X a, X b) { int i = (a) ? 1+a : 0; int j = (a&&b) ? a+b : i; if (a) { // ... } } 3 The conversion-type-id in a conversion-function-id is the longest pos sible sequence of conversion-declarators. This prevents ambiguities between the declarator operator * and its expression counterparts. For example: &ac.operator int*i; // syntax error: // parsed as: '&(ac.operator int *) i' // not as: '&(ac.operator int)*i' The * is the pointer declarator and not the multiplication operator. 4 Conversion operators are inherited. 5 Conversion functions can be virtual. 6 At most one user-defined conversion (constructor or conversion func tion) is implicitly applied to a single value. For example, class X { // ... public: operator int(); }; class Y { // ... public: operator X(); }; Y a; int b = a; // illegal: // a.operator X().operator int() not tried int c = X(a); // ok: a.operator X().operator int() 7 User-defined conversions are used implicitly only if they are unam biguous. A conversion function in a derived class does not hide a conversion function in a base class unless the two functions convert to the same type. For example, class X { public: // ... operator int(); }; class Y : public X { public: // ... operator void*(); }; void f(Y& a) { if (a) { // error: ambiguous // ... } } 12.4 Destructors [class.dtor] 1 A member function of class cl named ~cl is called a destructor; it is used to destroy values of type cl immediately before the object con taining them is destroyed. A destructor takes no parameters, and no return type can be specified for it (not even void). It is not possi ble to take the address of a destructor. A destructor can be invoked for a const or volatile object.3) A destructor may not be declared const or volatile (_class.this_). A destructor may not be static. _________________________ 3) Volatile semantics might or might not be used. 2 Destructors are not inherited. If a base or a member of a class has a destructor and no destructor is declared for the class itself a default destructor is generated. +------- BEGIN BOX 2 -------+ A default destructor should be generated if the class has a dealloca tion function. +------- END BOX 2 -------+ This generated destructor calls the destructors for bases and members of its class. Generated destructors are public. 3 The body of a destructor is executed before the destructors for member or base objects. Destructors for nonstatic member objects are exe cuted in reverse order of their declaration before the destructors for base classes. Destructors for nonvirtual base classes are executed in reverse order of their declaration in the derived class before destructors for virtual base classes. Destructors for virtual base classes are executed in the reverse order of their appearance in a depth-first left-to-right traversal of the directed acyclic graph of base classes; left-to-right is the order of appearance of the base class names in the declaration of the derived class. Destructors for elements of an array are called in reverse order of their construc tion. 4 A destructor may be declared virtual or pure virtual. In either case if any objects of that class or any derived class are created in the program the destructor must be defined. 5 Member functions may be called from within a destructor; see _class.cdtor_. 6 An object of a class with a destructor cannot be a member of a union. 7 Destructors are invoked implicitly (1) when an automatic variable (_basic.stc_) or temporary (_class.temporary_, _dcl.init.ref_) object goes out of scope, (2) for constructed static (_basic.stc_) objects at program termination (_basic.start_), and (3) through use of a delete- expression (_expr.delete_) for objects allocated by a new-expression (_expr.new_). Destructors can also be invoked explicitly. A delete- expression invokes the destructor for the referenced object and passes the address of its memory to a dealloation function (_expr.delete_, _class.free_). For example, class X { // ... public: X(int); ~X(); }; void g(X*); void f() // common use: { X* p = new X(111); // allocate and initialize g(p); delete p; // cleanup and deallocate } 8 Explicit calls of destructors are rarely needed. One use of such calls is for objects placed at specific addresses using a new- expression with the placement option. Such use of explicit placement and destruction of objects can be necessary to cope with dedicated hardware resources and for writing memory management facilities. For example, void* operator new(size_t, void* p) { return p; } void f(X* p); static char buf[sizeof(X)]; void g() // rare, specialized use: { X* p = new(buf) X(222); // use buf[] // and initialize f(p); p->X::~X(); // cleanup } 9 Invocation of destructors is subject to the usual rules for member functions, e.g., an object of the appropriate type is required (except invoking delete on a null pointer has no effect). When a destructor is invoked for an object, the object no longer exists; if the destruc tor is explicitly invoked again for the same object the behavior is undefined. For example, if the destructor for an automatic object is explicitly invoked, and the block is subsequently left in a manner that would ordinarily invoke implicit destruction of the object, the behavior is undefined. 10The notation for explicit call of a destructor may be used for any simple type name. For example, int* p; // ... p->int::~int(); Using the notation for a type that does not have a destructor has no effect. Allowing this enables people to write code without having to know if a destructor exists for a given type. 11The effect of destroying an object more than once is undefined. This implies that that explicitly destroying a local variable causes unde fined behavior on exit from the block, because exiting will attempt to destroy the variable again. This is true even if the block is exited because of an exception. 12.5 Free store [class.free] 1 When an object is created with a new-expression(_expr.new_), an allo cation function(operator new() for non-array objects or operator new[]() for arrays) is (implicitly) called to get the required storage (_basic.stc.dynamic.allocation_). 2 When a non-array object or an array of class T is created by a new- expression, the allocation function is looked up in the scope of class T using the usual rules. 3 When a new-expression is executed, the selected allocation function will be called with the amount of space requested (possibly zero) as its first argument. 4 Any allocation function for a class X is a static member (even if not explicitly declared static). 5 For example, class Arena; class Array_arena; struct B { void* operator new(size_t, Arena*); }; struct D1 : B { }; Arena* ap; Array_arena* aap; void foo(int i) { new (ap) D1; // calls B::operator new(size_t, Arena*) new D1[i]; // calls ::operator new[](size_t) new D1; // ill-formed: ::operator new(size_t) hidden } 6 When an object is deleted with a delete-expression(_expr.delete_), a deallocation function (operator delete() for non-array objects or operator delete[]() for arrays) is (implicitly) called to reclaim the storage occupied by the object. 7 When an object is deleted by a delete-expression, the deallocation function is looked up in the scope of class of the executed destructor (see _expr.delete_) using the usual rules. 8 When a delete-expression is executed, the selected deallocation func tion will be called with the address of the block of storage to be reclaimed as its first argument and (if the two-parameter style is used) the size of the block as its second argument.4) 9 Any deallocation function for a class X is a static member (even if not explicitly declared static). For example, _________________________ 4) If the static class in the delete-expression is different from the dynamic class and the destructor is not virtual the size might be in correct, but that case is already undefined. class X { // ... void operator delete(void*); void operator delete[](void*, size_t); }; class Y { // ... void operator delete(void*, size_t); void operator delete[](void*); }; 10Since member allocation and deallocation functions are static they cannot be virtual. However, the deallocation function actually called is determined by the destructor actually called, so if the destructor is virtual the effect is the same. For example, struct B { virtual ~B(); void operator delete(void*, size_t); }; struct D : B { void operator delete(void*); void operator delete[](void*, size_t); }; void f(int i) { B* bp = new D; delete bp; // uses D::operator delete(void*) D* dp = new D[i]; delete [] dp; // uses D::operator delete[](void*, size_t) } Here, storage for the non-array object of class D is deallocated by D::operator delete(), due to the virtual destructor. 11Access to the deallocation function is checked statically. Thus even though a different one may actually be executed, the statically visi ble deallocation function must be accessible. Thus in the example above, if B::operator delete() had been private, the delete expression would have been ill-formed. 12.6 Initialization [class.init] 1 A class having a user-defined constructor or having a non-trivial implicitly-declared default constructor is said to require non-trivial initialization. 2 An object of a class (or array thereof) with no private or protected non-static data members and that does not require non-trivial initial ization can be initialized using an initializer list; see _dcl.init.aggr_. An object of a class (or array thereof) with a user- declared constructor must either be initialized or have a default con structor (_class.ctor_) (whether user- or compiler-declared). The default constructor is used if the object (or array thereof) is not explicitly initialized. 12.6.1 Explicit initialization [class.expl.init] 1 Objects of classes with constructors (_class.ctor_) can be initialized with a parenthesized expression list. This list is taken as the argu ment list for a call of a constructor doing the initialization. Alternatively a single value is specified as the initializer using the = operator. This value is used as the argument to a copy constructor. Typically, that call of a copy constructor can be eliminated. For example, class complex { // ... public: complex(); complex(double); complex(double,double); // ... }; complex sqrt(complex,complex); complex a(1); // initialize by a call of // complex(double) complex b = a; // initialize by a copy of `a' complex c = complex(1,2); // construct complex(1,2) // using complex(double,double) // copy it into `c' complex d = sqrt(b,c); // call sqrt(complex,complex) // and copy the result into `d' complex e; // initialize by a call of // complex() complex f = 3; // construct complex(3) using // complex(double) // copy it into `f' Overloading of the assignment operator = has no effect on initializa tion. 2 The initialization that occurs in argument passing and function return is equivalent to the form T x = a; The initialization that occurs in new expressions (_expr.new_) and in base and member initializers (_class.base.init_) is equivalent to the form T x(a); 3 Arrays of objects of a class with constructors use constructors in initialization (_class.ctor_) just as do individual objects. If there are fewer initializers in the list than elements in the array, a default constructor (_class.ctor_) must be declared (whether by the compiler or the user), and it is used; otherwise the initializer- clause must be complete. For example, complex cc = { 1, 2 }; // error; use constructor complex v[6] = { 1,complex(1,2),complex(),2 }; Here, v[0] and v[3] are initialized with complex::complex(double), v[1] is initialized with complex::complex(double,double), and v[2], v[4], and v[5] are initialized with complex::complex(). 4 An object of class M can be a member of a class X only if (1) M has a default constructor, or (2) X has a user-declared constructor and if every user-declared constructor of class X specifies a ctor- initializer (_class.base.init_) for that member. In case 1 the default constructor is called when the aggregate is created. If a member of an aggregate has a destructor, then that destructor is called when the aggregate is destroyed. 5 Constructors for nonlocal static objects are called in the order they occur in a file; destructors are called in reverse order. See also _basic.start_, _stmt.dcl_, _class.static_. 12.6.2 Initializing bases and members [class.base.init] 1 The definition of a constructor can specify initializers for direct and virtual base classes and for members not inherited from a base class. This is most useful for class objects, constants, and refer ences where the semantics of initialization and assignment differ. A ctor-initializer has the form ctor-initializer: : mem-initializer-list mem-initializer-list: mem-initializer mem-initializer , mem-initializer-list mem-initializer: ::opt nested-name-specifieropt class-name ( expression-listopt ) identifier ( expression-listopt ) The argument list is used to initialize the named nonstatic member or base class object. This (or for an aggregate (_dcl.init.aggr_), ini tialization by a brace-enclosed list) is the only way to initialize nonstatic const and reference members. For example, struct B1 { B1(int); /* ... */ }; struct B2 { B2(int); /* ... */ }; struct D : B1, B2 { D(int); B1 b; const c; }; D::D(int a) : B2(a+1), B1(a+2), c(a+3), b(a+4) { /* ... */ } D d(10); First, the base classes are initialized in declaration order (indepen dent of the order of mem-initializers), then the members are initial ized in declaration order (independent of the order of mem- initializers), then the body of D::D() is executed (_class.ctor_). The declaration order is used to ensure that sub-objects and members are destroyed in the reverse order of initialization. 2 Virtual base classes constitute a special case. Virtual bases are constructed before any nonvirtual bases and in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes; left-to-right is the order of appearance of the base class names in the declaration of the derived class. 3 The class of a complete object (_intro.memory_) is said to be the most derived class for the sub-objects representing base classes of the object. All sub-objects for virtual base classes are initialized by the constructor of the most derived class. If a constructor of the most derived class does not specify a mem-initializer for a virtual base class then that virtual base class must have a default construc tor. Any mem-initializers for virtual classes specified in a con structor for a class that is not the class of the complete object are ignored. For example, class V { public: V(); V(int); // ... }; class A : public virtual V { public: A(); A(int); // ... }; class B : public virtual V { public: B(); B(int); // ... }; class C : public A, public B, private virtual V { public: C(); C(int); // ... }; A::A(int i) : V(i) { /* ... */ } B::B(int i) { /* ... */ } C::C(int i) { /* ... */ } V v(1); // use V(int) A a(2); // use V(int) B b(3); // use V() C c(4); // use V() 4 In a ctor-initializer, the effect of calling a non-static member func tion of a class object whose base classes have not all been initial ized is undefined. For example, class A { public: A(int x); }; class B : public A { public: int f(); B() : A(f()) {} // undefined: calls B::f() but B's A not yet initialized }; A mem-initializer is evaluated in the scope of the constructor in which it appears. For example, class X { int a; public: const int& r; X(): r(a) {} }; initializes X::r to refer to X::a for each object of class X. 5 The identifier of a ctor-initializer's mem-initializer in a class' constructor is looked up in the scope of the class. It must denote a nonstatic data member or the type of a direct or virtual base class. For the purpose of this name lookup, the name, if any, of each class is considered a nested class member of that class. A constructor's mem-initializer-list can initialize a base class using any name that denotes that base class type; the name used may differ from the class definition. The type shall not designate both a direct non-virtual base class and an inherited virtual base class. For example: struct A { A(); }; struct B: public virtual A { }; struct C: public A, public B { C(); }; C::C(): A() { } // ill-formed: which A? 12.7 Constructors and destructors [class.cdtor] 1 Member functions may be called in constructors and destructors. This implies that virtual functions may be called (directly or indirectly). The function called will be the one defined in the constructor's (or destructor's) own class or its bases, but not any function overriding it in a derived class. This ensures that unconstructed parts of objects will not be accessed in the body of the constructor or destructor. For example, class X { public: virtual void f(); X() { f(); } // calls X::f() ~X() { f(); } // calls X::f() }; class Y : public X { int& r; public: void f() { r++; // disaster if `r' is uninitialized } Y(int& rr) :r(rr) {} // calls X::X() which calls X::f() }; 2 The effect of calling a pure virtual function directly or indirectly for the object being constructed from a constructor, except using explicit qualification, is undefined (_class.abstract_). 12.8 Copying class objects [class.copy] 1 A class object can be copied in two ways, by assignment (_expr.ass_) and by initialization (_class.ctor_, _dcl.init_) including function argument passing (_expr.call_) and function value return (_stmt.return_). Conceptually, for a class X these two operations are implemented by an assignment operator and a copy constructor (_class.ctor_). If not declared by the programmer, they will if pos sible be automatically defined (synthesized) as memberwise assignment and memberwise initialization of the base classes and non-static data members of X, respectively. An explicit declaration of either of them will suppress the synthesized definition. 2 If all bases and members of a class X have copy constructors accepting const parameters, the synthesized copy constructor for X will have a single parameter of type const X&, as follows: X::X(const X&) Otherwise it will have a single parameter of type X&: X::X(X&) and programs that attempt initialization by copying of const X objects will be ill-formed. 3 Similarly, if all bases and members of a class X have assignment oper ators accepting const parameters, the synthesized assignment operator for X will have a single parameter of type const X&, as follows: X& X::operator=(const X&) Otherwise it will have a single parameter of type X&: X& X::operator=(X&) and programs that attempt assignment by copying of const X objects will be ill-formed. The synthesized assignment operator will return a reference to the object for which is invoked. 4 Objects representing virtual base classes will be initialized only once by a generated copy constructor. Objects representing virtual base classes will be assigned only once by a generated assignment operator. 5 Memberwise assignment and memberwise initialization implies that if a class X has a member or base of a class M, M's assignment operator and M's copy constructor are used to implement assignment and initializa tion of the member or base, respectively, in the synthesized opera tions. The default assignment operation cannot be generated for a class if the class has: --a non-static data member that is a const or a reference, --a non-static data member or base class whose assignment operator is inaccessible to the class, or --a non-static data member or base class with no assignment operator for which a default assignment operation cannot be generated. Similarly, the default copy constructor cannot be generated for a class if a non-static data member or a base of the class has an inaccessible copy constructor, or has no copy constructor and the default copy constructor cannot be generated for it. 6 The default assignment and copy constructor will be declared, but they will not be defined (that is, a function body generated) unless needed. That is, X::operator=() will be generated only if no assign ment operation is explicitly declared and an object of class X is assigned an object of class X or an object of a class derived from X or if the address of X::operator= is taken. Initialization is handled similarly. 7 If implicitly declared, the assignment and the copy constructor will be public members and the assignment operator for a class X will be defined to return a reference of type X& referring to the object assigned to. 8 If a class X has any X::operator=() that has a parameter of class X, the default assignment will not be generated. If a class has any copy constructor defined, the default copy constructor will not be gener ated. For example, class X { // ... public: X(int); X(const X&, int = 1); }; X a(1); // calls X(int); X b(a, 0); // calls X(const X&, int); X c = b; // calls X(const X&, int); 9 Assignment of class objects X is defined in terms of X::operator=(const X&). This implies (_class.conv_) that objects of a derived class can be assigned to objects of a public base class. For example, class X { public: int b; }; class Y : public X { public: int c; }; void f() { X x1; Y y1; x1 = y1; // ok y1 = x1; // error } Here y1.b is assigned to x1.b and y1.c is not copied. 10Copying one object into another using the default copy constructor or the default assignment operator does not change the structure of either object. For example, struct s { virtual f(); // ... }; struct ss : public s { f(); // ... }; void f() { s a; ss b; a = b; // really a.s::operator=(b) b = a; // error a.f(); // calls s::f b.f(); // calls ss::f (s&)b = a; // assign to b's s part // really ((s&)b).s::operator=(a) b.f(); // still calls ss::f } The call a.f() will invoke s::f() (as is suitable for an object of class s (_class.virtual_)) and the call b.f() will call ss::f() (as is suitable for an object of class ss).