【问题标题】:C++ constructor calling and object creationC++ 构造函数调用和对象创建
【发布时间】:2011-09-17 19:51:36
【问题描述】:
class Test{
    public :
        int x;  
        Test()
        {
            x = 0;
            cout<<"constructor with no arguments called"<<endl;
        }
        Test(int xx)
        {
            x = xx;
            cout<<"constructor with single int argument called"<<endl;
        }
        
};


int main()
{
    Test a(10);
    Test aa = 10;
}

输出: 程序编译输出

调用单个 int 参数的构造函数

调用单个 int 参数的构造函数

现在

class Test{
    public :
        int x;  
        Test()
        {
            x = 0;
            cout<<"constructor with no arguments called"<<endl;
        }
        Test(int xx)
        {
            x = xx;
            cout<<"constructor with single int argument called"<<endl;
        }
        
        Test( Test& xx)
        {
            x = xx.x;
            cout<<"copy constructor called"<<endl;
        }
        
        
};


int main()
{
    Test a(10);
    Test aa = 10;
}

编译失败。

constructorinvokings.cc:36:7: error: no viable constructor copying variable of type 'Test'
        Test aa = 10;
             ^    ~~
constructorinvokings.cc:23:3: note: candidate constructor not viable: no known conversion from 'Test' to 'Test &' for 1st
      argument
                Test( Test& xx)
                ^
1 error generated.

我是 C++ 新手。

不是测试 a(10) 并且测试 aa = 10;一样吗?

为什么添加复制构造函数与 Test aa=10 冲突?

如果我将 Test(Test& xx) 修改为 Test(const Test& xx) 它正在工作。但是,当我们尝试使用整数参数调用构造函数时,为什么编译器会检查复制构造函数签名。

请澄清

提前致谢。

【问题讨论】:

标签: c++ initialization copy-constructor


【解决方案1】:

到目前为止,您得到的所有答案都过于复杂和/或有些误导。

在第一次构造/初始化中:

T a(10);

非常明显的事情发生了。第二个构造/初始化更有趣:

T aa = 10;

这相当于:

T aa(T(10));

意思是你创建一个T类型的临时对象,然后构造aa作为这个临时对象的副本。这意味着调用了复制构造函数。

C++ 有一个默认的复制构造函数,当您没有明确声明时,它会为一个类创建它。所以即使class T 的第一个版本没有声明复制构造函数,仍然有一个。编译器声明的那个有这个签名T(const T &amp;)

现在,在您声明看起来像复制构造函数的第二种情况下,您将参数设为T &amp;,而不是const T &amp;。这意味着编译器在尝试编译第二个表达式时会尝试使用您的复制构造函数,但它不能。它抱怨您声明的复制构造函数需要一个非常量参数,而它给出的参数是 const。所以它失败了。

另一条规则是,在编译器将您的初始化转换为T aa(T(10)); 之后,就可以将其转换为T aa(10);。这称为“复制省略”。在某些情况下,允许编译器跳过对复制构造函数的调用。但它只能在验证表达式格式正确并且在复制构造函数调用仍然存在时不会生成任何编译器错误之后才会这样做。所以这是一个优化步骤,它可能会影响程序的哪些部分运行,但不能影响哪些程序是有效的,哪些是错误的(至少从它们是否编译的角度来看)。

【讨论】:

  • 所以在第二种情况下,如果调用复制构造函数来从临时对象创建新对象,为什么程序只输出“调用单个 int 参数的构造函数”
  • @Vineel Kumar Reddy:因为编译器随后意识到它可以省略复制构造函数。首先,它必须确保表达式按原样有效,然后它可以确定是否有任何复制构造函数调用是多余的。它删除(又名省略)多余的。有一些特定的规则来管理哪些复制构造函数调用可以被认为是多余的。
【解决方案2】:

测试 a(10) 和测试 aa = 10;不相同。第一个从 10 中构造一个测试,而第二个从 10 中构造一个测试 and 然后复制构造 aa 。添加复制构造函数会发生冲突,因为它声明复制构造对于两个操作数都是可变操作。在某些情况下,复制构造函数将源代码作为非 const 引用,但情况有些复杂,在这里不是你的情况。

编辑:请注意,在第二种情况下,编译器可以省略该复制构造,并且通常会这样做。

【讨论】:

  • 另外,考虑在构造函数中寻找initialization lists
  • @Nicol Bolas:我说的是复制分配吗?它是复制构造函数。
  • @Paul:是的。您的复制构造函数将const 带到那里(然后实际上省略了副本,如我的回答中所述)。在 OP 的情况下,没有带有const ref 参数的 CC,由于 RHS 上的右值,这是必需的......是的,即使副本将被省略,它必须仍然可行 i>.
  • @Paul:是的。正如在另一个答案中指出的那样,允许编译器省略该复制构造,并且通常会这样做。将更新我的答案,不包括那个。
  • @Vineel Kumar Reddy:因为,如前所述,编译器可以省略复制构造函数调用(作为优化),而且通常会这样做。
【解决方案3】:

(不那么冗长的测试用例here。)

这两种初始化形式其实根本不完全等价。

前者是你的沼泽标准结构:

T o(10);

然而,后者更复杂。

T o = 10;

这里,RHS 上的表达式 (10) 被转换为类型 T,然后对象 o 从该转换后的表达式复制构造。此处无法进行复制构造,因为您的T(T&amp;) 禁止合成隐式T(T const&amp;),并且RHS 上的临时对象不能绑定到非引用const

现在,允许编译器省略该复制构造,这通常会发生,但转换必须仍然有效,否则程序格式错误。


[n3290: 8.5/13]:初始化的形式(使用括号或=) 通常是微不足道的,但是当初始化器或 被初始化的实体有一个类类型;见下文。 [..]

[n3290: 8.5/14]:表单中发生的初始化

T x = a;

以及参数传递、函数返回、抛出异常 (15.1)、处理异常 (15.3)、聚合成员 初始化(8.5.1)称为复制初始化。 [ 注意: 复制初始化可能会调用移动(12.8)。 ——尾注 ]

[n3290: 8.5/16]: 初始化器的语义如下。 [..] 如果目标类型是(可能是 cv 限定的)类类型:[..] 否则(即,对于剩余的复制初始化案例), 用户定义的转换序列,可以从源转换 类型为目标类型或(使用转换函数时) 其派生类的枚举如 13.3.1.4 中所述, 最好的一个是通过重载决议(13.3)选择的。 如果 转换无法完成或不明确,初始化是 格式不正确。 [..]

[n3290: 12.8/31]: 当满足某些条件时,一个实现 允许省略类对象的复制/移动构造,甚至 如果对象的复制/移动构造函数和/或析构函数有 副作用。 [..] 这种省略复制/移动操作,称为复制 省略,在以下情况下是允许的(可能是 合并以消除多个副本):[..]

  • 当临时类对象尚未绑定到引用时 (12.2)将被复制/移动到具有相同的类对象 cv-unqualified 类型,复制/移动操作可以省略 将临时对象直接构造到 省略复制/移动。 [..]

(我实际上找不到明确指出复制构造函数必须仍然可以访问的引文,即使副本被省略,但确实如此。)

【讨论】:

  • 错误提示:“没有从 'Test' 到 'Test &' 的已知转换”。这是否意味着编译器确实能够从int 创建T,但不能将该临时T 传递给获得非常量T&amp; 的复制构造函数?
  • 如果从intT 的转换是问题所在,为什么它在第一个代码示例中有效?转换也将在那里发生。
  • intTest 的转换没有问题。问题是与右值一起使用的非 const 复制构造函数参数。
  • @bshields:给你同样的评论。来吧,人们!
  • 我发表评论时还没有更正。我在发布之前刷新了以确保。
【解决方案4】:

我不同意 K-ballo。当您使用单个参数定义 c'tor 时,编译器可能会 [mis] 使用它进行隐式转换。在这种情况下会发生什么。

因此,当您运行程序时,它会调用“c'tor with single arg”来构建 AWA aa。

测试 aa = 10;

本声明不涉及“复制”或“转让”。 鉴于您为编译器提供了单个 arg c'tor,在这种情况下它会 [mis] 使用它。

// So basically compiler converts this:
Test aa = 10;
// to
Test aa(10);

您可以通过将该 c'tor 标记为显式来停止滥用。只需将Test( int x ) 更改为explicit Test( int x ) 在此处查看显式关键字的一些解释:What does the explicit keyword mean in C++?

如果我将 Test(Test& xx) 修改为 Test(const Test& xx) 它正在工作。但是,当我们尝试使用整数参数调用构造函数时,为什么编译器会检查复制构造函数签名。

我也不明白这一点,想知道。 :)

我观察到根本不会调用 copy c'tor。

#include<iostream>
using namespace std;

class Test
{
public :
    int x;
    Test() { x=0; cout << "    Test() - " << x << endl;}
    Test(int xx) { x=xx; cout << "    Test(int xx) - " << x << endl;}
    Test(Test& xx){ x=xx.x; cout << "    Test(Test& xx) - " << x << endl;}
    Test(const Test& xx){ x=xx.x; cout << "    Test(const Test& xx) - " << x << endl;}
    Test& operator= (const Test& xx) { x=xx.x; cout << "    Test& operator= (const Test& xx) - " << x << endl; return *this;}
};


int main()
{
    cout << "--Test a(10);--" << endl;
    Test a(10);
    cout << "--Test aa = 20;--" << endl;
    Test aa = 20;
    cout << "--Test aaa = aa;--" << endl;
    Test aaa = aa;
    cout << "--aaa = aa;--" << endl;
    aaa = aa;
    cout << "--aaa = 30;--" << endl;
    aaa = 30;
}
/*
OUTPUT:
    --Test a(10);--
        Test(int xx) - 10
    --Test aa = 20;--
        Test(int xx) - 20
    --Test aaa = aa;--
        Test(Test& xx) - 20
    --aaa = aa;--
        Test& operator= (const Test& xx) - 20
    --aaa = 30;--
        Test(int xx) - 30
        Test& operator= (const Test& xx) - 30
*/

当使用 const arg Test(const Test&amp; xx) 复制 c'tor 时:

我们得到编译错误。

D:\Workspaces\CodeBlocks\Test\main.cpp: In function 'int main()':
D:\Workspaces\CodeBlocks\Test\main.cpp:21:19: error: no matching function for call to 'Test::Test(Test)'
D:\Workspaces\CodeBlocks\Test\main.cpp:10:9: note: candidates are: Test::Test(Test&)
D:\Workspaces\CodeBlocks\Test\main.cpp:9:9: note:                 Test::Test(int)
D:\Workspaces\CodeBlocks\Test\main.cpp:8:9: note:                 Test::Test()
Process terminated with status 1 (0 minutes, 0 seconds)
4 errors, 0 warnings

第 21 行是Test aa = 20;

【讨论】:

    猜你喜欢
    • 2012-02-05
    • 1970-01-01
    • 2013-09-24
    • 2019-07-25
    • 2011-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多