【问题标题】:Can I call a constructor from another constructor (do constructor chaining) in C++?我可以从 C++ 中的另一个构造函数(做构造函数链接)调用构造函数吗?
【发布时间】:2010-09-23 10:10:15
【问题描述】:

作为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”关键字,但都失败了。

【问题讨论】:

  • 在引用的上下文中使用 thisauto 将是未来重构目的的有趣关键字。

标签: c++ constructor


【解决方案1】:

C++11:是的!

C++11 及更高版本具有相同的功能(称为delegating constructors)。

语法与C#略有不同:

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

C++03:否

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

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

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

    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)
    {
      // ...
    }
    

参考the C++FAQ entry

【讨论】:

  • 实际上,非常显着的默认参数使得我们能够以一种非常干净的方式来完成我们在 C# 中调用 this() 时通常会完成的工作
  • 请注意,不使用 C++11 的建议解决方案仅在要构造的类没有继承或常量字段时才有效。我没有找到在初始化列表之外初始化父类和常量字段的方法。
  • @bobobobo 使用默认参数会将它们编译到调用者中,所以这不是非常干净的。重载更多的是代码,没错,但是实现封装了默认值。
  • 使用 init() 的一个缺点是你不能声明一个指针或 ref 是 const (因为在 ref/pointer 中是 const 而不是它指向的东西)如果你不在构造函数()中初始化它。
  • @gen(除了缺少的第二个冒号)它会创建一个临时的 Foo 然后立即丢弃它。
【解决方案2】:

是和否,取决于 C++ 的版本。

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

这在 C++11(又名 C++0x)中发生了变化,增加了对以下语法的支持:
(示例取自Wikipedia

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

【讨论】:

  • 但这与标准默认参数语法有何不同?
  • @TomášZato 不能使用默认参数做的一件事是使用您的参数调用另一个构造函数:SomeType(string const &s) { /*...*/ } SomeType(char const *pc) : SomeType(string(pc)) { /*...*/ }
  • @TomášZato 另一个区别是,对于默认参数,您只有一个构造函数,您必须将其设为公共、受保护或私有,而使用 2 个构造函数,一个调用另一个,您可以限制对一个的访问其中一个,而不必限制对另一个的访问。
  • PS:当然,您也可以使用由多个构造函数调用的私有 init 函数来执行此操作,但这不适用于初始化列表。
  • 它也不同于默认值,因为您可以更改它而无需重新编译使用该库的代码。使用默认值,这些值被“烘焙”到调用中。
【解决方案3】:

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

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

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

【讨论】:

  • 好点;大多数人只是说“不,你不能”。我能 :)。我做了这个切换回来,并使用原来的ctor来决定调用哪个。在调试中,可以在第二个中看到对象,一切都被初始化,但在返回时返回默认值。想想就觉得很有意义。
  • 这不是“调用构造函数”。 only 你可以直接“调用构造函数”的地方是在 C++11 的 ctor-initializer 中。你在这个例子中所做的是构造一个对象,它是一个不同的鱼壶。不要被它看起来 像对构造函数的函数调用这一事实所误导,因为它不是 的!事实上,没有办法对构造函数进行函数调用,这就是为什么不可能构造一个类的实例,其唯一的构造函数是无法推导出模板参数的函数模板的实例化。
  • (也就是说,在语法上不可能显式地向构造函数提供模板参数。)
  • 实际上有一种方法可以对构造函数进行函数调用 - 使用放置new 语法。不过,这通常不是您想要的。 (而且它没有做任何事情来允许您显式提供模板参数。)
  • 使用位置 new 仍会创建一个 new 对象,尽管它位于相同的内存位置。但是,尽管是不同的对象,并且可以将证明这一点的代码放在一起。
【解决方案4】:

C++11:是的!

C++11 及更高版本具有相同的功能(称为delegating constructors)。

语法与C#略有不同:

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

C++03:否

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

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

但是,不,在 C++03 之前,您不能调用同一类的另一个构造函数。

【讨论】:

  • 你错了。您可以调用同一类的构造函数。将使用其参数列表来确定调用哪个构造函数。执行 B(int x, inty) :B(x) 将首先调用带有签名 B(int x) 的构造函数。
  • 是的。但我在 2008 年 11 月是正确的,在 C++11 发布之前。
【解决方案5】:

C++11constructor can call another constructor overload

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) {}
};

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

【讨论】:

    【解决方案6】:

    如果你想作恶,你可以使用就地“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 的解决方案要好得多。

    【讨论】:

    • 似乎不建议这样做,因为您可以在 10.3 末尾阅读 parashift.com/c++-faq-lite/ctors.html#faq-10.3
    • 在我看来唯一的缺点是它增加了一点开销; new(this) 测试 this==NULL 是否存在,如果是则跳过构造函数。
    • 这几乎肯定是 UB。
    • 这真是太邪恶了。假设您在该构造函数中分配内存,并在析构函数中释放它。不会释放任何内存。
    • 但是如果你显式调用析构函数,你仍然可以避免灾难:this-&gt;~Foo();,在new (this) Foo();之前
    【解决方案7】:

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

    C++11 引入delegating constructors

    委托构造函数

    如果类本身的名称在 成员初始值设定项列表,则该列表必须由该成员组成 仅初始化器;这样的构造函数被称为委托 构造函数,以及由唯一成员选择的构造函数 初始化列表是目标构造函数

    在这种情况下,目标构造函数是通过重载选择的 解决并首先执行,然后控制返回到 委托构造函数及其主体被执行。

    委托构造函数不能递归。

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

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

    【讨论】:

      【解决方案8】:

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

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

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

      【讨论】:

        【解决方案9】:

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

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

        如果您有许多必须调用其“下一级”对应项的构造函数,这可能会变得一团糟,但对于少数构造函数,它应该是可行的。

        【讨论】:

          【解决方案10】:

          在 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.:坦率地说,我很惊讶之前没有提到这一点。

          【讨论】:

          • 我在 Ubuntu (4.4.3) 下的 g++ 上试过这个。它不起作用:在构造函数“Vertex::Vertex()”中:错误:“class Vertex”的使用无效。
          • 我在 Visual Studio 2003 .NET Architect 版本下对其进行了测试 - 工作正常。
          • 这种方法很危险!如果成员不是来自 POD 类型,则会产生内存泄漏。例如 std::string.
          • 坦率地说,我对 Visual C++ 允许这样做感到震惊和失望。它非常破碎。我们不要说服人们使用这种策略。
          • 这类似于placement new吗?
          【解决方案11】:

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

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

          private:
              istream * in;
                // More private fields
          

          我们要定义两个构造函数:

          public:
              StreamArrayReader(istream * in_stream);
              StreamArrayReader(char * filepath);
              // More constructors...
          

          第二个简单地利用了第一个(当然我们不想重复前者的实现)。理想情况下,一个人想做这样的事情:

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

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

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

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

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

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

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

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

          【讨论】:

          • 在我看来,使用朋友方法比普通的私有方法没有优势。你为什么要这么做?
          【解决方案12】:

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

          Foo::Foo()
          {
              // do what every Foo is needing
              ...
          }
          
          Foo::Foo(char x)
          {
              *this = Foo();
          
              // do the special things for a Foo with char
              ...
          }
          

          【讨论】:

            【解决方案13】:

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

            如果那是您正在寻找的东西,那么不,那是不可能的。

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

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

            但你可能没有多个构造函数,然后分别调用它们。

            【讨论】:

            • 他在问一个构造函数是否可以调用另一个构造函数。 Java 和 C# 允许这样做。
            【解决方案14】:

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

            【讨论】:

            • 你不能“调用构造函数”;请在ohlemacher的回答中查看我的cmets。但是,您基本上是正确的。
            • 构造函数只是一个初始化器,所以在构造器之外创建通用初始化器是一种老式的方法。在调用构造函数之前分配内存,通常在调用 operator new 或 malloc 时...
            【解决方案15】:

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

            #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 std 编译它: g++ main.cpp -std=c++98 -o test_1

            你会看到:

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

            所以:)

            【讨论】:

            • 这不是最开始的问题,他问的不是调用基类构造函数,而是同一个类中的另一个构造函数。
            猜你喜欢
            • 1970-01-01
            • 2011-03-24
            • 2010-11-02
            • 1970-01-01
            • 1970-01-01
            • 2010-12-15
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多