Inside the C++ Object Model 读书笔记(五)

Chapter 5. Semantics of Construction, Destruction, and Copy

One may both define and invoke a pure virtual function, provided it is invoked statically and not through the virtual mechanism.

class Abstract_base
{
    //  ...
    virtual void interface() = 0;
};

inline void 
Abstract_base::interface() 
{ 
   // ... 
} 
inline void 
Concrete_derived::interface() 
{ 
   // ok: static invocation 
   Abstract_base::interface(); 

   // ... 
} 

Pure virtual destructor must be defined by the class designer. Why? Every derived class destructor is internally augmented to statically invoke each of its virtual base and immediate base class destructors. The absence of a definition of any of the base class destructors in general results in a link-time error.

5.1 Object Construction without Inheritance

In C, global is treated as a tentative definition because it is without explicit initialization. A tentative definition can occur multiple times within the program. Those multiple instances are collapsed by the link editor, and a single instance is placed within the portion of the program data segment reserved for uninitialized global objects (for historical reasons, it’s called the BSS, an abbreviation of Block Started by Symbol, an IBM 704 assembler pseudo-op).

In C++, tentative definitions are not supported because of the implicit application of class constructors. (Admittedly the language could have distinguished between class objects and Plain Ol’ Data, but doing so seemed an unnecessary complication.) global, therefore, is treated within C++ as a full definition (precluding a second or subsequent definition). One difference between C and C++, then, is the relative unimportance of the BSS data segment in C++. All global objects within C++ are treated as initialized.

void foobar( Point &__result ) 
{ 
  Point local; 
  local.Point::Point( 0.0, 0.0 ); 

  // heap remains the same ... 

  // application of copy constructor 
  __result.Point::Point( local ); 

  // destruction of local object would go here 
  // had Point defined a destructor: 
  // local.Point::~Point(); 

  return; 
} 

And if the named return value (NRV) optimization is supported, the function is further transformed as follows:

// Pseudo C++ code: transformation of foobar() 
// to support named return value optimization 

void foobar( Point &__result ) 
{ 
   __result.Point::Point( 0.0, 0.0 ); 

   // heap remains the same ... 

   return; 
} 

it makes good sense to provide a copy constructor even if the default memberwise semantics are sufficient. Its presence triggers the application of the NRV optimization. Moreover, as I showed in the previous example, its application removes the need for invoking the copy constructor, since the results are being directly computed within the object to be returned.

5.2 Object Construction under Inheritance

virtual inheritance

class Point3d : public virtual Point;
class Vertex   : virtual public Point { ... }; 
class Vertex3d : public Point3d, public Vertex { ... }; 
class PVertex  : public Vertex3d { ... }; 

The constructor for Vertex must also invoke the Point class constructor. However, when Point3d and Vertex are subobjects of Vertex3d, their invocations of the Point constructor must not occur; rather, Vertex3d, as the most-derived class, becomes responsible for initializing Point. In the subsequent PVertex derivation, it, not Vertex3d, is responsible for the initialization of the shared Point subobject.

Within a subsequently derived class, such as Vertex3d, the invocation of the Point3d and Vertex constructors always sets the __most_derived argument to false, thus suppressing the Point constructor invocation within both constructors.

// Psuedo C++ Code: 
// Constructor Augmentation with Virtual Base class 
Vertex3d* 
Vertex3d::Vertex3d( Vertex3d *this, bool __most_derived, 
        float x, float y, float z ) 
{ 
   if ( __most_derived != false ) 
          this->Point::Point( x, y); 

   // invoke immediate base classes, 
   // setting __most_derived to false 

   this->Point3d::Point3d( false, x, y, z ); 
   this->Vertex::Vertex( false, x, y ); 

   // set vptrs 
   // insert user code 

   return this; 
} 

Leveraging this knowledge, we can generate better-performing constructors at the expense of generating more program text. Some newer implementations split each constructor into a complete object and a subobject instance. The complete object version unconditionally invokes the virtual base constructors, sets all vptrs, and so on. The subobject version does not invoke the virtual base class constructors, may possibly not set the vptrs, and so on.

The Semantics of the vptr Initialization

The general algorithm of constructor execution is as follows:

  1. Within the derived class constructor, all virtual base class and then immediate base class constructors are invoked.
  2. That done, the object’s vptr(s) are initialized to address the associated virtual table(s).
  3. The member initialization list, if present, is expanded within the body of the constructor. This must be done after the vptr is set in case a virtual member function is called.
  4. The explicit user-supplied code is executed.

5.3 Object Copy Semantics

Disallowing the copying of one class object with another is accomplished by declaring the copy assignment operator private and not providing a definition.

The Standard speaks of copy assignment operators’ not exhibiting bitwise copy semantics as nontrivial. In practice, only nontrivial instances are synthesized.

. Note that we still may want to provide a copy constructor in order to turn on the named return value (NRV) optimization.

// class Vertex : virtual public Point 
inline Vertex& 
Vertex::operator=( const Vertex &v ) 
{ 
   this->Point::operator=( v ); 
   _next = v._next; 
   return *this; 
} 
Now let's derive Vertex3d from Point3d and Vertex. Here is the Vertex3d copy assignment operator:

inline Vertex3d& 
Vertex3d::operator=( const Vertex3d &v ) 
{ 
   this->Point::operator=( v ); 
   this->Point3d::operator=( v ); 
   this->Vertex::operator=( v ); 
... 
} 

How is the compiler going to suppress the user-programmed instances of the Point copy assignment operator within the Point3d and Vertex copy assignment operators? The compiler can’t duplicate the traditional constructor solution of inserting additional arguments.

Alternatively, the compiler might generate split functions for the copy assignment operator to support the class as the most derived and as an intermediate base class. The split function solution is reasonably well defined if the copy assignment operator is generated by the compiler; it is not well defined if programmed by the designer of the class.

Actually, the copy assignment operator is ill behaved under virtual inheritance and needs to be carefully designed and documented. In practice, many compilers don’t even try to get the semantics right. They invoke each virtual base instance within each intermediate copy assignment operator, thus causing multiple instances of the virtual base class copy assignment operator to be invoked.
What does the Standard have to say about this?

It is unspecified whether subojects representing virtual base classes are assigned more than once by the implicitly defined copy assignment operator (Section 12.8).

Admittedly, it is a weakness in the language and something one should always examine carefully in code reviews of designs that use virtual base classes.

5.4 Object Efficiency

5.5 Semantics of Destruction

If a destructor is not defined by a class, the compiler synthesizes one only if the class contains either a member or base class with a destructor.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值