在C++中,我可以从另一个构造函数中调用构造函数(进行构造函数链)吗?

1138
作为一名C#开发者,我习惯于运行构造函数:
class Test {
    public Test() {
        DoSomething();
    }

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

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

有没有办法在C++中做到这一点?
我尝试调用类名并使用'this'关键字,但都失败了。

1
在所提到的上下文中使用thisauto将是未来重构目的的有趣关键字。 - sergiol
15个回答

1511

C++11: 是的!

从C++11开始,它也拥有了这个功能(称为委托构造函数)。语法与C#略有不同:

class Foo {
public: 
  Foo(char x, int y) {}
  Foo(int y) : Foo('a', y) {}
};

C++03:不支持

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

  1. 您可以通过默认参数结合两个(或更多)构造函数:

  2. class Foo {
    public:
      Foo(char x, int y=0);  // combines two constructors (char) and (char, int)
      // ...
    };
    
    使用初始化方法来共享通用代码:
    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条目


90
默认参数实际上为我们完成在C#中使用this()所能实现的功能提供了一个非常简洁的方法。 - bobobobo
6
请注意,不使用C++11的提议解决方案仅适用于构建类没有继承或常量字段的情况。我没有找到一种方法来在初始化列表之外初始化父类和常量字段。 - greydet
13
使用默认参数会将它们编译到调用者中,这样不太干净。重载需要更多代码,但实现封装了默认值。 - Eugene Ryabtsev
4
使用init()的一个缺点是,如果你没有在constructor()中进行初始化,那么你不能声明一个指针或引用为const(即指针/引用是const而不是它所指向的内容)。 - locka
3
(除第二个冒号缺失外)它将创建一个临时的 Foo,然后立即丢弃它。 - Jim Balter
显示剩余15条评论

148

视C++版本而定,有些可以有些不行。

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

这在C++11(又称C++0x)中有所改变,添加了对以下语法的支持:
(示例取自维基百科

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

3
那么这与标准默认参数语法有什么不同呢? - Tomáš Zato
1
@TomášZato 默认参数无法使用您的参数调用其他构造函数:SomeType(string const &s) { /*...*/ } SomeType(char const *pc) : SomeType(string(pc)) { /*...*/ } - Cyrille Ka
8
另一个不同之处在于,默认参数只需要一个构造函数,你必须将其设置为public、protected或private之一,而有2个构造函数,其中一个调用另一个,则可以限制对其中一个构造函数的访问,而无需同时限制对另一个构造函数的访问。 - Kaiserludi
PS:当然,你也可以使用一个私有的初始化函数来被多个构造函数调用,但这对于初始化列表是无效的。 - Kaiserludi
2
它也不同于默认值,因为您可以在不重新编译使用库的代码的情况下更改它。对于默认值,这些值被“烘焙”到调用中。 - Rptx

52

我认为您可以从构造函数中调用另一个构造函数。代码可以编译和运行。我最近看到有人这么做了,而且在Windows和Linux上都能运行。

但它并不会做你想要的事情。内部构造函数将构造一个临时局部对象,在外部构造函数返回后被删除。它们必须是不同的构造函数,否则您将创建一个递归调用。

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


3
好观点;大多数人只是说“不行”。我可以: )。我通过切换回来,并使用原始构造函数来决定调用哪个函数。在调试中,对象可以在第二个函数中看到,一切都被初始化了,但返回时会返回默认值。如果你考虑一下,这是很有道理的。 - ChiefTwoPencils
12
这不是“调用构造函数”。在C++11中,你唯一可以直接“调用构造函数”的地方是在_ctor-initializer_中。在这个例子中,你正在构造一个对象,这是完全不同的事情。不要被它看起来像构造函数的函数调用所迷惑,因为它不是一个!实际上没有办法调用构造函数,这就是为什么无法构造只有函数模板实例化作为其构造函数的类的实例,而这些函数模板参数不能被推导。 - Lightness Races in Orbit
1
(也就是说,在构造函数中显式提供模板参数是语法上不可能的。) - Lightness Races in Orbit
实际上,有一种方法可以使用放置 new 语法来调用构造函数。但通常这不是你想要的。(而且它也不能让你显式地提供模板参数。) - celticminstrel
1
使用放置new仍然会创建一个“新”的对象,尽管在相同的内存位置。但是它仍然是一个不同的对象,并且可以组合证明这一点的代码。 - Leon

43
值得指出的是,你可以在你的构造函数中调用父类的构造函数,例如:
class A { /* ... */ };

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

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

你错了。你可以调用同一类的构造函数。它将使用其参数列表来确定要调用哪个构造函数。执行B(int x, inty) : B(x)将首先调用具有签名B(int x)的构造函数。 - SomeDutchGuy
21
是的。但在2008年11月,C++11发布之前,我的观点是正确的。 - kchoose2

25

C++11 中,一个构造函数可以调用另一个构造函数的重载形式

class Foo  {
     int d;         
public:
    Foo  (int i) : d(i) {}
    Foo  () : Foo(42) {} //New to C++11
};

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

class Foo  {
     int d = 5;         
public:
    Foo  (int i) : d(i) {}
};

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


17

如果你想要做恶意的事情,你可以使用就地"in-place" "new"运算符:

class Foo() {
    Foo() { /* default constructor deliciousness */ }
    Foo(Bar myParam) {
      new (this) Foo();
      /* bar your param all night long */
    } 
};

对我来说似乎起作用。

编辑

正如@ElvedinHamzagic所指出的,如果Foo包含分配内存的对象,则该对象可能无法被释放。这进一步使问题复杂化。

一个更一般的例子:

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 */
    } 
};

看起来确实不太优雅。@JohnIdol的解决方案好得多。


4
似乎根据10.3节的末尾所述,这并不是建议做的事情。请参考http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.3。 - Stormenet
2
这几乎肯定是未定义行为。 - Lightness Races in Orbit
4
这真的很邪恶。假设你正在构造函数中分配内存,并在析构函数中释放它。那么没有任何内存会被释放。 - Elvedin Hamzagic
2
但是,如果您在 new (this) Foo(); 之前显式调用析构函数:this->~Foo();,则仍然可以从灾难中逃脱。 - Elvedin Hamzagic
@lyngvi 无法编辑我的先前评论... 不管怎样,这似乎是新位置:https://isocpp.org/wiki/faq/dtors#placement-new - Stormenet
显示剩余5条评论

13
简单来说,在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.

class Foo {
public: 
  Foo(char x, int y) {}
  Foo(int y) : Foo('a', y) {} // Foo(int) delegates to Foo(char,int)
};
请注意,委托构造函数是一个全新的提案;如果一个构造函数委托给另一个构造函数,则调用构造函数不允许在其初始化列表中拥有任何其他成员。这在考虑只需一次初始化常量/引用成员时是有意义的。

“调用构造函数不允许在其初始化列表中拥有任何其他成员”是什么意思? - SodaCris

11

不, 在C ++中,您不能从构造函数调用另一个构造函数。正如Warren所指出的那样,您可以:

  • 使用不同的签名重载构造函数
  • 在参数上使用默认值,以使“更简单”的版本可用

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


7

还有一种未展示的选择,就是将你的类分成两个部分,在原始类的外面包装一个轻量级的接口类,以达到你想要的效果:

class Test_Base {
    public Test_Base() {
        DoSomething();
    }
};

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

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

如果你有很多构造函数需要调用它们上一层的"下一个级别"的构造函数,这可能会变得混乱,但对于少数几个构造函数来说,它应该是可行的。

5
在Visual C++中,您还可以在构造函数内使用以下表示法:this->Classname::Classname(另一个构造函数的参数)。请参见下面的示例:
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#中一样。

P.S.:坦白地说,我很惊讶这个之前没有被提到过。


我在Visual Studio 2003 .NET Architect版下测试过了,运行良好。 - izogfif
2
这个方法非常危险!如果成员不是POD类型,它会产生内存泄漏。例如std::string。 - Alexander Drichel
5
坦率地说,我感到惊讶和失望,Visual C++居然允许这样。这是非常有问题的。让我们不要鼓励人们使用这种策略。 - Lightness Races in Orbit
这个与放置 new 相似吗? - pqnet
1
@LightnessRacesinOrbit:C++允许使用成员访问语法查找静态类成员(作为通常的类名+作用域运算符的替代方法)。我记不清这是否也适用于嵌套类型和typedef。 "注入类名"规则意味着类的名称可以被视为成员名称找到。 - Ben Voigt
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接