我可以从C++中的另一个构造函数(构造函数链接)调用构造函数吗?

Can I call a constructor from another constructor (do constructor chaining) in C++?

作为一个C开发人员,我经常运行构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Test {
    public Test() {
        DoSomething();
    }

    public Test(int count) : this() {
        DoSomethingWithCount(count);
    }

    public Test(int count, string name) : this(count) {
        DoSomethingWithName(name);
    }
}

有没有办法在C++中做到这一点?

我尝试调用类名并使用"this"关键字,但都失败了。


C++ 11:是的!

C++ 11和向前具有相同的特性(称为委托构造函数)。

语法与C稍有不同:

1
2
3
4
5
class Foo {
public:
  Foo(char x, int y) {}
  Foo(int y) : Foo('a', y) {}
};

C++ 03:没有

不幸的是,在C++ 03中没有办法做到这一点,但是有两种方法来模拟这一点:

  • 可以通过默认参数组合两个(或多个)构造函数:

    1
    2
    3
    4
    5
    class Foo {
    public:
      Foo(char x, int y=0);  // combines two constructors (char) and (char, int)
      // ...
    };
  • 使用init方法共享公共代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class Foo {
    public:
      Foo(char x);
      Foo(char x, int y);
      // ...
    private:
      void init(char x, int y);
    };

    Foo::Foo(char x)
    {
      init(x, int(x) + 7);
      // ...
    }

    Foo::Foo(char x, int y)
    {
      init(x, y);
      // ...
    }

    void Foo::init(char x, int y)
    {
      // ...
    }
  • 参阅C++ FAQ条目以供参考。


    不,不能在C++ 03中调用另一个构造函数(称为委托构造函数)。

    这在C++ 11中改变了(AKC+0x),它增加了对以下语法的支持:(摘自维基百科)

    1
    2
    3
    4
    5
    6
    7
    8
    class SomeType
    {
      int number;

    public:
      SomeType(int newNumber) : number(newNumber) {}
      SomeType() : SomeType(42) {}
    };


    我相信您可以从构造函数调用构造函数。它将编译并运行。我最近看到有人这样做,它运行在Windows和Linux上。

    它只是不做你想做的。内部构造函数将构造一个临时本地对象,一旦外部构造函数返回,该对象将被删除。它们也必须是不同的构造函数,否则您将创建一个递归调用。

    参考:https://isocpp.org/wiki/faq/ctors init methods


    值得指出的是,您可以在构造函数中调用父类的构造函数,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class A { /* ... */ };

    class B : public A
    {
        B() : A()
        {
            // ...
        }
    };

    但是,不,不能调用同一类的另一个构造函数。


    在C++ 11中,构造函数可以调用另一个构造函数重载:

    1
    2
    3
    4
    5
    6
    class Foo  {
         int d;        
    public:
        Foo  (int i) : d(i) {}
        Foo  () : Foo(42) {} //New to C++11
    };

    此外,还可以像这样初始化成员。

    1
    2
    3
    4
    5
    class Foo  {
         int d = 5;        
    public:
        Foo  (int i) : d(i) {}
    };

    这样就不需要创建初始化助手方法。建议不要在构造函数或析构函数中调用任何虚拟函数,以避免使用任何可能未初始化的成员。


    如果你想成为恶魔,你可以使用"新"操作符:

    1
    2
    3
    4
    5
    6
    7
    class Foo() {
        Foo() { /* default constructor deliciousness */ }
        Foo(Bar myParam) {
          new (this) Foo();
          /* bar your param all night long */
        }
    };

    似乎对我有用。

    编辑

    正如@elvedinhamzagic指出的那样,如果foo包含一个分配内存的对象,则该对象可能不会被释放。这使事情进一步复杂化。

    一个更一般的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Foo() {
    private:
      std::vector<int> Stuff;
    public:
        Foo()
          : Stuff(42)
        {
          /* default constructor deliciousness */
        }

        Foo(Bar myParam)
        {
          this->~Foo();
          new (this) Foo();
          /* bar your param all night long */
        }
    };

    当然,看起来不那么优雅。@约翰尼多的解决方案要好得多。


    不,在C++中,你不能从构造函数调用构造函数。正如沃伦指出的,你能做的是:

    • 使用不同的签名重载构造函数
    • 对参数使用默认值,以使"简单"版本可用

    注意,在第一种情况下,不能通过从另一个构造函数调用一个构造函数来减少代码重复。当然,您可以有一个独立的、私有的/受保护的方法来完成所有初始化,并让构造函数主要处理参数处理。


    在VisualC++中,你也可以在构造函数中使用这个符号:-> CordNoe::Classname(另一个构造函数的参数)。请参见下面的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Vertex
    {
     private:
      int x, y;
     public:
      Vertex(int xCoo, int yCoo): x(xCoo), y(yCoo) {}
      Vertex()
      {
       this->Vertex::Vertex(-1, -1);
      }
    };

    我不知道它是否在别的地方工作,我只在Visual C++ 2003和2008中测试过。你也可以用这种方式调用几个构造函数,就像Java和C语言一样。

    旁白:坦率地说,我很惊讶之前没有提到这一点。


    简单地说,你不能在C++ 11之前。

    C++ 11介绍委托构造函数:

    Delegating constructor

    If the name of the class itself appears as class-or-identifier in the
    member initializer list, then the list must consist of that one member
    initializer only; such constructor is known as the delegating
    constructor, and the constructor selected by the only member of the
    initializer list is the target constructor

    In this case, the target constructor is selected by overload
    resolution and executed first, then the control returns to the
    delegating constructor and its body is executed.

    Delegating constructors cannot be recursive.

    1
    2
    3
    4
    5
    class Foo {
    public:
      Foo(char x, int y) {}
      Foo(int y) : Foo('a', y) {} // Foo(int) delegates to Foo(char,int)
    };

    请注意,委托构造函数是一个"全有"或"无"建议;如果一个构造函数委托给另一个构造函数,则不允许调用构造函数在其初始化列表中包含任何其他成员。如果您考虑初始化const/reference成员一次,并且只初始化一次,那么这是有意义的。


    另一个尚未显示的选项是将类拆分为两个类,在原始类周围包装一个轻量级接口类,以实现您所期望的效果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Test_Base {
        public Test_Base() {
            DoSomething();
        }
    };

    class Test : public Test_Base {
        public Test() : Test_Base() {
        }

        public Test(int count) : Test_Base() {
            DoSomethingWithCount(count);
        }
    };

    如果有许多构造函数必须调用它们的"下一级"对应项,这可能会变得混乱,但是对于少数构造函数,这应该是可行的。


    这种方法可能适用于某些类型的类(当赋值运算符的行为"良好"时):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Foo::Foo()
    {
        // do what every Foo is needing
        ...
    }

    Foo::Foo(char x)
    {
        *this = Foo();

        // do the special things for a Foo with char
        ...
    }


    我建议使用private friend方法,该方法实现构造函数的应用逻辑,并由各种构造函数调用。下面是一个例子:

    假设我们有一个名为StreamArrayReader的类,其中包含一些私有字段:

    1
    2
    3
    private:
        istream * in;
          // More private fields

    我们要定义两个构造器:

    1
    2
    3
    4
    public:
        StreamArrayReader(istream * in_stream);
        StreamArrayReader(char * filepath);
        // More constructors...

    第二个简单地使用第一个(当然,我们不想重复前一个的实现)。理想情况下,人们希望做如下事情:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    StreamArrayReader::StreamArrayReader(istream * in_stream){
        // Implementation
    }

    StreamArrayReader::StreamArrayReader(char * filepath) {
        ifstream instream;
        instream.open(filepath);
        StreamArrayReader(&instream);
        instream.close();
    }

    但是,这在C++中是不允许的。因此,我们可以定义一个私有的friend方法,它实现第一个构造函数应该做的事情:

    1
    2
    private:
      friend void init_stream_array_reader(StreamArrayReader *o, istream * is);

    现在这个方法(因为它是朋友)可以访问o的私有字段。然后,第一个构造函数变为:

    1
    2
    3
    StreamArrayReader::StreamArrayReader(istream * is) {
        init_stream_array_reader(this, is);
    }

    请注意,这不会为新创建的副本创建多个副本。第二个是:

    1
    2
    3
    4
    5
    6
    StreamArrayReader::StreamArrayReader(char * filepath) {
        ifstream instream;
        instream.open(filepath);
        init_stream_array_reader(this, &instream);
        instream.close();
    }

    也就是说,不是让一个构造函数调用另一个构造函数,而是同时调用一个私人朋友!


    调用构造函数时,它实际上从堆栈或堆分配内存。因此,在另一个构造函数中调用构造函数将创建本地副本。所以我们在修改另一个对象,而不是我们关注的对象。


    如果我正确地理解了你的问题,你会问你是否可以调用C++中的多个构造函数?

    如果这就是你要找的,那就不可能了。

    当然,可以有多个构造函数,每个构造函数都有唯一的参数签名,然后在实例化新对象时调用所需的构造函数。

    甚至可以在末尾有一个带有默认参数的构造函数。

    但是您可能没有多个构造函数,然后分别调用它们中的每一个。


    比决定更容易测试:)试试这个:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #include <iostream>

    class A {
    public:
        A( int a) : m_a(a) {
            std::cout <<"A::Ctor" << std::endl;    
        }
        ~A() {
            std::cout <<"A::dtor" << std::endl;    
        }
    public:
        int m_a;
    };

    class B : public A {
    public:
        B( int a, int b) : m_b(b), A(a) {}
    public:
        int m_b;
    };

    int main() {
        B b(9, 6);
        std::cout <<"Test constructor delegation a =" << b.m_a <<"; b =" << b.m_b << std::endl;    
        return 0;
    }

    并用98标准进行编译:G++MIN .CPP-STD= C++ 98 -O Test1

    你会看到:

    1
    2
    3
    A::Ctor
    Test constructor delegation a = 9; b = 6
    A::dtor

    所以:)