Читаем Thinking In C++. Volume 2: Practical Programming полностью

  Bottom(int i, int j, int k, int m)

  : Top(i), Left(0, j), Right(0, k) { w = m; }

  friend ostream&

  operator<<(ostream& os, const Bottom& b) {

    return os << b.x << ',' << b.y << ',' << b.z

      << ',' << b.w;

  }

};

int main() {

  Bottom b(1, 2, 3, 4);

  cout << sizeof b << endl;

  cout << b << endl;

  cout << static_cast(&b) << endl;

  Top* p = static_cast(&b);

  cout << *p << endl;

  cout << static_cast(p) << endl;

  cout << dynamic_cast(p) << endl;

} ///:~

Each virtual base of a given type refers to the same object, no matter where it appears in the hierarchy.[111] This means that when a Bottom object is instantiated, the object layout may look something like this:

The Left and Right subobjects each have a pointer (or some conceptual equivalent) to the shared Top subobject, and all references to that subobject in Left and Right member functions will go through those these pointers.[112] In this case, there is no ambiguity when upcasting from a Bottom to a Top object, since there is only one Top object to convert to.

The output of the previous program is as follows:

36

1,2,3,4

1245032

1

1245060

1245032

The addresses printed suggest that this particular implementation does indeed store the Top subobject at the end of the complete object (although it’s not really important where it goes). The result of a dynamic_cast to void* always resolves to the address of the complete object.

We made the Top destructor virtual so we could apply the dynamic_cast operator. If you remove that virtual destructor (and the dynamic_cast statement so the program will compile), the size of Bottom decreases to 24 bytes. That seems to be a decrease equivalent to the size of three pointers. What gives?

It’s important not to take these numbers too literally. Other compilers we use manage only to increase the size by four bytes when the virtual constructor is added. Not being compiler writers, we can’t tell you their secrets. We can tell you, however, that with multiple inheritance, a derived object must behave as if it has multiple VPTRs, one for each of its direct base classes that also have virtual functions. It’s as simple as that. Compilers can make whatever optimizations its authors can invent, but the behavior must be the same.

Certainly the strangest thing in the previous code is the initializer for Top in the Bottom constructor. Normally one doesn’t worry about initializing subobjects beyond direct base classes, since all classes take care of initializing their own bases. There are, however, multiple paths from Bottom to Top, so relying on the intermediate classes Left and Right to pass along the necessary initialization data results in an ambiguity (whose responsibility is it?)! For this reason, it is always the responsibility of the most derived class to initialize a virtual base. But what about the expressions in the Left and Right constructors that also initialize Top? They are certainly necessary when creating standalone Left or Right objects, but must be ignored when a Bottom object is created (hence the zeros in their initializers in the Bottom constructor—any values in those slots are ignored when the Left and Right constructors execute in the context of a Bottom object). The compiler takes care of all this for you, but it’s important to understand where the responsibility lies. Always make sure that all concrete (nonabstract) classes in a multiple inheritance hierarchy are aware of any virtual bases and initialize them appropriately.

These rules of responsibility apply not only to initialization but to all operations that span the class hierarchy. Consider the stream inserter in the previous code. We made the data protected so we could "cheat" and access inherited data in operator<<(ostream&, const Bottom&). It usually makes more sense to assign the work of printing each subobject to its corresponding class and have the derived class call its base class functions as needed. What would happen if we tried that with operator<<( ), as the following code illustrates?

//: C09:VirtualBase2.cpp

// Shows how not to implement operator<<

#include

using namespace std;

class Top {

  int x;

public:

  Top(int n) { x = n; }

  friend ostream&

  operator<<(ostream& os, const Top& t) {

    return os << t.x;

  }

};

class Left : virtual public Top {

  int y;

public:

  Left(int m, int n) : Top(m) { y = n; }

  friend ostream&

  operator<<(ostream& os, const Left& l) {

    return os << static_cast(l) << ',' << l.y;

  }

};

class Right : virtual public Top {

  int z;

public:

  Right(int m, int n) : Top(m) { z = n; }

  friend ostream&

  operator<<(ostream& os, const Right& r) {

    return os << static_cast(r) << ',' << r.z;

  }

};

class Bottom : public Left, public Right {

  int w;

public:

  Bottom(int i, int j, int k, int m)

  : Top(i), Left(0, j), Right(0, k) { w = m; }

  friend ostream&

Перейти на страницу:

Похожие книги

1С: Бухгалтерия 8 с нуля
1С: Бухгалтерия 8 с нуля

Книга содержит полное описание приемов и методов работы с программой 1С:Бухгалтерия 8. Рассматривается автоматизация всех основных участков бухгалтерии: учет наличных и безналичных денежных средств, основных средств и НМА, прихода и расхода товарно-материальных ценностей, зарплаты, производства. Описано, как вводить исходные данные, заполнять справочники и каталоги, работать с первичными документами, проводить их по учету, формировать разнообразные отчеты, выводить данные на печать, настраивать программу и использовать ее сервисные функции. Каждый урок содержит подробное описание рассматриваемой темы с детальным разбором и иллюстрированием всех этапов.Для широкого круга пользователей.

Алексей Анатольевич Гладкий

Программирование, программы, базы данных / Программное обеспечение / Бухучет и аудит / Финансы и бизнес / Книги по IT / Словари и Энциклопедии
1С: Управление торговлей 8.2
1С: Управление торговлей 8.2

Современные торговые предприятия предлагают своим клиентам широчайший ассортимент товаров, который исчисляется тысячами и десятками тысяч наименований. Причем многие позиции могут реализовываться на разных условиях: предоплата, отсрочка платежи, скидка, наценка, объем партии, и т.д. Клиенты зачастую делятся на категории – VIP-клиент, обычный клиент, постоянный клиент, мелкооптовый клиент, и т.д. Товарные позиции могут комплектоваться и разукомплектовываться, многие товары подлежат обязательной сертификации и гигиеническим исследованиям, некондиционные позиции необходимо списывать, на складах периодически должна проводиться инвентаризация, каждая компания должна иметь свою маркетинговую политику и т.д., вообщем – современное торговое предприятие представляет живой организм, находящийся в постоянном движении.Очевидно, что вся эта кипучая деятельность требует автоматизации. Для решения этой задачи существуют специальные программные средства, и в этой книге мы познакомим вам с самым популярным продуктом, предназначенным для автоматизации деятельности торгового предприятия – «1С Управление торговлей», которое реализовано на новейшей технологической платформе версии 1С 8.2.

Алексей Анатольевич Гладкий

Финансы / Программирование, программы, базы данных