zoukankan      html  css  js  c++  java
  • Advanced C++ | Virtual Constructor

      Can we make a class constructor virtual in C++ to create polymorphic objects?

      No. C++ being static 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 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 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 virtual constructor is to decouple object creation from it’s type.

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

     1 #include <iostream>
     2 using namespace std;
     3 
     4 //// LIBRARY START
     5 class Base
     6 {
     7 public:
     8     Base() 
     9     { 
    10     }
    11     virtual ~Base() // Ensures to invoke actual object destructor
    12     { 
    13     }
    14     
    15     // An interface
    16     virtual void DisplayAction() = 0;
    17 };
    18 
    19 class Derived1 : public Base
    20 {
    21 public:
    22     Derived1()
    23     {
    24         cout << "Derived1 created" << endl;
    25     }
    26     
    27     ~Derived1()
    28     {
    29         cout << "Derived1 destroyed" << endl;
    30     }
    31     
    32     void DisplayAction()
    33     {
    34         cout << "Action from Derived1" << endl;
    35     }
    36 };
    37 
    38 class Derived2 : public Base
    39 {
    40 public:
    41     Derived2()
    42     {
    43         cout << "Derived2 created" << endl;
    44     }
    45     
    46     ~Derived2()
    47     {
    48         cout << "Derived2 destroyed" << endl;
    49     }
    50     
    51     void DisplayAction()
    52     {
    53         cout << "Action from Derived2" << endl;
    54     }
    55 };
    56 
    57 //// LIBRARY END
    58 
    59 class User
    60 {
    61 public:
    62     
    63     // Creates Drived1
    64     User() : pBase(0)
    65     {
    66         // What if Derived2 is required? - Add an if-else ladder (see next sample)
    67         pBase = new Derived1();
    68     }
    69     
    70     ~User()
    71     {
    72         if( pBase )
    73         {
    74             delete pBase;
    75             pBase = 0;
    76         }
    77     }
    78     
    79     // Delegates to actual object
    80     void Action()
    81     {
    82         pBase->DisplayAction();
    83     }
    84     
    85 private:
    86     Base *pBase;
    87 };
    88 
    89 int main()
    90 {
    91     User *user = new User();
    92     
    93     // Need Derived1 functionality only
    94     user->Action();
    95     
    96     delete user;
    97 }

      Output:

      Derived1 created
      Action from Derived1
      Derived1 destroyed

      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,

      1 #include <iostream>
      2 using namespace std;
      3  
      4 //// LIBRARY START
      5 class Base
      6 {
      7 public:
      8     Base() { }
      9  
     10     virtual // Ensures to invoke actual object destructor
     11     ~Base() { }
     12  
     13     // An interface
     14     virtual void DisplayAction() = 0;
     15 };
     16  
     17 class Derived1 : public Base
     18 {
     19 public:
     20     Derived1()
     21     {
     22         cout << "Derived1 created" << endl;
     23     }
     24  
     25     ~Derived1()
     26     {
     27         cout << "Derived1 destroyed" << endl;
     28     }
     29  
     30     void DisplayAction()
     31     {
     32         cout << "Action from Derived1" << endl;
     33     }
     34 };
     35  
     36 class Derived2 : public Base
     37 {
     38 public:
     39     Derived2()
     40     {
     41         cout << "Derived2 created" << endl;
     42     }
     43  
     44     ~Derived2()
     45     {
     46         cout << "Derived2 destroyed" << endl;
     47     }
     48  
     49     void DisplayAction()
     50     {
     51         cout << "Action from Derived2" << endl;
     52     }
     53 };
     54  
     55 //// LIBRARY END
     56  
     57 class User
     58 {
     59 public:
     60  
     61     // Creates Derived1 or Derived2 based on input
     62     User() : pBase(0)
     63     {
     64         int input; // ID to distinguish between
     65                    // Derived1 and Derived2
     66  
     67         cout << "Enter ID (1 or 2): ";
     68         cin  >> input;
     69  
     70         while( (input !=  1) && (input !=  2) )
     71         {
     72             cout << "Enter ID (1 or 2 only): ";
     73             cin  >> input;
     74         }
     75  
     76         if( input == 1 )
     77         {
     78             pBase = new Derived1;
     79         }
     80         else
     81         {
     82             pBase = new Derived2;
     83         }
     84  
     85         // What if Derived3 being added to the class hierarchy?
     86     }
     87  
     88     ~User()
     89     {
     90         if( pBase )
     91         {
     92             delete pBase;
     93             pBase = 0;
     94         }
     95     }
     96  
     97     // Delegates to actual object
     98     void Action()
     99     {
    100         pBase->DisplayAction();
    101     }
    102  
    103 private:
    104     Base *pBase;
    105 };
    106  
    107 int main()
    108 {
    109     User *user = new User();
    110  
    111     // Need either Derived1 or Derived2 functionality
    112     user->Action();
    113  
    114     delete user;
    115 }

      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.

     1 #include <iostream>
     2 using namespace std;
     3  
     4 class User
     5 {
     6 public:
     7     User() : pBase(0)
     8     {
     9         // Creates Drived1 or Derived2 based on need
    10  
    11         int input; // ID to distinguish between
    12                    // Derived1 and Derived2
    13  
    14         cout << "Enter ID (1 or 2): ";
    15         cin  >> input;
    16  
    17         while( (input !=  1) && (input !=  2) )
    18         {
    19             cout << "Enter ID (1 or 2 only): ";
    20             cin  >> input;
    21         }
    22  
    23         if( input == 1 )
    24         {
    25             pBase = new Derived1;
    26         }
    27         else if( input == 2 )
    28         {
    29             pBase = new Derived2;
    30         }
    31         else
    32         {
    33             pBase = new Derived3;
    34         }
    35     }
    36  
    37     ~User()
    38     {
    39         if( pBase )
    40         {
    41             delete pBase;
    42             pBase = 0;
    43         }
    44     }
    45  
    46     // Delegates to actual object
    47     void Action()
    48     {
    49         pBase->DisplayAction();
    50     }
    51  
    52 private:
    53     Base *pBase;
    54 };

      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,

      1 #include <iostream>
      2 using namespace std;
      3 
      4 //// LIBRARY START
      5 class Base
      6 {
      7 public:
      8     
      9     // The "Virtual Constructor"
     10     static Base *Create(int id);
     11     
     12     Base() 
     13     { 
     14     }
     15     
     16     virtual ~Base()  // Ensures to invoke actual object destructor 
     17     { 
     18     }
     19     
     20     // An interface
     21     virtual void DisplayAction() = 0;
     22 };
     23 
     24 class Derived1 : public Base
     25 {
     26 public:
     27     Derived1()
     28     {
     29         cout << "Derived1 created" << endl;
     30     }
     31     
     32     ~Derived1()
     33     {
     34         cout << "Derived1 destroyed" << endl;
     35     }
     36     
     37     void DisplayAction()
     38     {
     39         cout << "Action from Derived1" << endl;
     40     }
     41 };
     42 
     43 class Derived2 : public Base
     44 {
     45 public:
     46     Derived2()
     47     {
     48         cout << "Derived2 created" << endl;
     49     }
     50     
     51     ~Derived2()
     52     {
     53         cout << "Derived2 destroyed" << endl;
     54     }
     55     
     56     void DisplayAction()
     57     {
     58         cout << "Action from Derived2" << endl;
     59     }
     60 };
     61 
     62 class Derived3 : public Base
     63 {
     64 public:
     65     Derived3()
     66     {
     67         cout << "Derived3 created" << endl;
     68     }
     69     
     70     ~Derived3()
     71     {
     72         cout << "Derived3 destroyed" << endl;
     73     }
     74     
     75     void DisplayAction()
     76     {
     77         cout << "Action from Derived3" << endl;
     78     }
     79 };
     80 
     81 // We can also declare "Create" outside Base
     82 // But it is more relevant to limit it's scope to Base
     83 Base *Base::Create(int id)
     84 {
     85     // Just expand the if-else ladder, if new Derived class is created
     86     // User code need not be recompiled to create newly added class objects
     87     
     88     if( id == 1 )
     89     {
     90         return new Derived1;
     91     }
     92     else if( id == 2 )
     93     {
     94         return new Derived2;
     95     }
     96     else
     97     {
     98         return new Derived3;
     99     }
    100 }
    101 //// LIBRARY END
    102 
    103 //// UTILITY START
    104 class User
    105 {
    106 public:
    107     User() : pBase(0)
    108     {
    109         // Receives an object of Base heirarchy at runtime
    110         
    111         int input;
    112         
    113         cout << "Enter ID (1, 2 or 3): ";
    114         cin >> input;
    115         
    116         while( (input !=  1) && (input !=  2) && (input !=  3) )
    117         {
    118             cout << "Enter ID (1, 2 or 3 only): ";
    119             cin >> input;
    120         }
    121         
    122         // Get object from the "Virtual Constructor"
    123         pBase = Base::Create(input);
    124     }
    125     
    126     ~User()
    127     {
    128         if( pBase )
    129         {
    130             delete pBase;
    131             pBase = 0;
    132         }
    133     }
    134     
    135     // Delegates to actual object
    136     void Action()
    137     {
    138         pBase->DisplayAction();
    139     }
    140     
    141 private:
    142     Base *pBase;
    143 };
    144 
    145 //// UTILITY END
    146 
    147 //// Consumer of User (UTILITY) class
    148 int main()
    149 {
    150     User *user = new User();
    151     
    152     // Action required on any of Derived objects
    153     user->Action();
    154     
    155     delete user;
    156 }

      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.

      Pattern world demonstrate 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, interested reader can refer them for more information.

      这篇文章讲述的是有设计模式中的工厂模式Factory Pattern,这个我不太懂哦。

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

      转载请注明:http://www.cnblogs.com/iloveyouforever/

      2013-11-26  21:00:57

  • 相关阅读:
    【笔记】黄如花.信息检索.学习心得
    【心得】Lattice和Xilinx工具关键特性对比(Diamond、ISE)
    【导航】FPGA相关
    【笔记】黄如花.信息检索.前4章心得(新增大牛汇总的公开课资源)
    python正则表达式练习题
    python正则表达式(1)--特殊字符
    【转】什么时候 i = i + 1 并不等于 i += 1?
    Linux查看文件指定行数内容
    python mysqldb批量执行语句executemany
    linux命令行常用快捷键
  • 原文地址:https://www.cnblogs.com/iloveyouforever/p/3444115.html
Copyright © 2011-2022 走看看