继续问复制控制

Dan1980 2007-01-05 04:10:21
一个非常简单的程序如下:

#include <iostream>

using namespace std;

class Foo {
public:
Foo(int i) { cout << "using int. done." << endl; }
~Foo() { cout << "deleting. done." << endl; }
//private:
Foo(const Foo &f) { cout << "using copying. done." << endl; }
};

int main() {
Foo f = 100;
return 0;
}


运行结果为:
using int. done.
deleting. done.

分析Foo f = 100这句,应该是先调用Foo(int)来构造临时对象,再调用拷贝构造函数Foo(const Foo &f)来构造f,然后再删除临时对象。

但奇怪的是:拷贝构造函数Foo(const Foo &f)似乎并没有被调用,析构函数也只被调用了一次;而如果将Foo(const Foo &f)设为private,则不能编译。

谁能解释一下?谢谢
...全文
373 点赞 收藏 21
写回复
21 条回复
切换为时间正序
请发表友善的回复…
发表回复
Dan1980 2007-02-14
自己找到答案了:

《C++ Primer》上说:

The copy form of initialization is primarily supported for compatibility with C usage. When it can do so, the compiler is permitted (but not obligated) to skip the copy constructor and create the object directly.
复制形式的初始化(即用=号初始化,本人注)主要是为了兼容C的用法的。当这种用法被支持时,编译器被允许(但不被制强)跳过复制构造函数而直接创建该对象。

我想这段话已经回答我的问题了。

感谢楼上诸位,特别是abblly(东边日出西边雨)。

结贴。
回复
abblly 2007-01-05
楼上的是翻译还是自己写的,乱七八糟的.
-----------
不是我自己写的,我还没有这么高的水平~-~
转载别人的,哪里乱了,那篇文章把对象的构造
T a;
T a(v);
T a = v;
的各种情况都说到了,而且是参考的C++标准,感觉说的既全又对阿
回复
htqx 2007-01-05

我认为这个规定毫无必要.

T v = 0;
T v(0);

唯一的差别就是explicit 关键字.
回复
htqx 2007-01-05
楼上的是翻译还是自己写的,乱七八糟的.
回复
abblly 2007-01-05
不过vc6也有问题,Foo f = 100; 这句它可能直接解释为Foo f(100);了,但根据《C++ Primer》中的介绍,应该不是这样的。
----------------
不是应该不是这样的,详见我转载文章的第三种情况
----
3。T a = v;
....
之所以强调语义上,是因为标准说了这个调用可以优化掉...
--------------
回复
Dan1980 2007-01-05
to abblly(西边日出东边雨):

哦,那有可能是g++的问题了。

不过vc6也有问题,Foo f = 100; 这句它可能直接解释为Foo f(100);了,但根据《C++ Primer》中的介绍,应该不是这样的。
回复
abblly 2007-01-05
Foo f = 100;
等於
Foo f(100);
是这样吗?
----------
no,
Foo f = 100;相当于我转载文章的第三种情况
Foo f(100);//我转载文章的第二种情况


回复
linjuan0215 2007-01-05
Foo f = 100;
等於
Foo f(100);
是这样吗?


回复
lw1a2 2007-01-05
有个帖子,回家找找看看
回复
abblly 2007-01-05
to Dan1980() ( ) 信誉:98 Blog
这个是实现相关的,我用vc6可以编译通过
你可以看看我转载的回答
回复
taodm 2007-01-05
抱歉,我只有纸版。
回复
Dan1980 2007-01-05
to taodm(taodm):
手上没有execptional C++,能否将相关内容转述一下?
回复
Dan1980 2007-01-05
to abblly(西边日出东边雨)
--------------------------------
are u sure???
--------------------------------

Yes, i am sure. you may try it yourself.




to zhousqy(标准C匪徒)(甩拉,甩拉)
------------------------------------
Foo f = 100;
等於
Foo f(100);
------------------------------------
既然是这样,那为什么将Foo(const Foo &f)设为private就不能编译呢?


回复
abblly 2007-01-05
转载:

发信人: NetMD (见到了传说中的神!!), 信区: CPlusPlus
标 题: [FAQ]T a(v);和T a=v;的区别(3)
发信站: 水木社区 (Fri Mar 31 12:18:10 2006), 站内

本文所说的类型T均指UDT,非built-in类型
感谢ilovecpp,所有“*注”部分由他提醒而补充

构造一个对象,有如下三种形式:

1。T a;
这个没什么好说的,调用default ctor来构造a
不过要注意的是,要么T就一个ctor也没有,编译器合成default ctor,即T::T()
如果T有手动添加的其他形式的ctor,但是没有T::T(),则此语句报错,因为编译器不再
为T合成default ctor
*注1,如果没有default ctor,但是有某个ctor的所有参数都有缺省值,则T a;也成立


2。T a(v);
这个语句显式用v作为参数调用T的某个最适合的可单参数调用的ctor来构造a
该语句形式被C++标准称为direct-initialization
这里强调两点,一个是显式,这说明这种方式构造a的话可以调用被explicit声明的ctor
第二点是最适合,这是由overload rules决定的,而不是那么想当然,示例代码如下
struct T
{
T(){}
T(int){}
operator int(){return 0;}
private:
T(T&){}
};
T foo() {return T();}
int main()
{
T a;

T b(a); // 编译出错,最适合的T(T&)不可访问,为private

T c(foo());// 编译成功,foo()返回的临时对象是rvalue,不能绑定到non-const引用
// 因此T(T&)不是由overload rules选定的最适合ctor
// 但是rvalue可以调用一次自定义隐式转换cast到int然后调用T(int)构造c
// 也就是说,overload rules选择了T(int)

return 0;
}
这段代码说明,即使类型相同,调用的也不一定是copy ctor
如果有人怀疑T(T&)不是copy ctor,认为T(const T&)才是,请参阅C++标准12.8


3。T a = v;
该语句形式被C++标准称为copy-initialization,这个名称有很大的迷惑性,导致本人曾
一度认为该式语义上(稍后解释什么是语义上)必须要调用copy ctor
参照C++标准8.5/14,有这么一句话
If the destination type is a (possibly cv-qualified) class type:
--If the initialization is direct-initialization, or if it is
copy-initialization where the cv-unqualified version of the
source type is the same class as, or a derived class of, the
class of the destination, constructors are considered. The
applicable constructors are enumerated (13.3.1.3), and the
best one is chosen through overload resolution (13.3). The
constructor so selected is called to initialize the object,
with the initializer expression(s) as its argument(s). If no
constructor applies, or the overload resolution is ambiguous,
the initialization is ill-formed.
简单点说,上面的意思就是,如果T是UDT,在“copy-initialization且v的cv-
unqualified类型也为T(或者T的派生类)”的情况下,执行方式同
direct-initialization一样!
*注2,实际上该情况中的copy-initialization同direct-initialization还是有区别的,
* C++标准指出,只有T a(v);的方式才是显式调用ctor,否则为隐式调用,后者要求
* 被选中的ctor不能被声明为explicit,否则编译出错
*---C++标准12.3.1
* An explicit constructor constructs objects just like non-explicit
* constructors, but does so only where the direct-initialization
* syntax (8.5) or where casts (5.2.9, 5.4) are explicitly used.
C++标准还规定,如果非上述情况,则有
--Otherwise (i.e., for the remaining copy-initialization cases),
user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in 13.3.1.4,
and the best one is chosen through overload resolution (13.3). If the
conversion cannot be done or is ambiguous, the initialization is ill-formed.
The function selected is called with the initializer expression as its
argument; if the function is a constructor, the call initializes a
temporary of the destination type. The result of the call (which is
the temporary for the constructor case) is then used to direct-initialize,
according to the rules above, the object that is the destination of
the copy-initialization. In certain cases, an implementation is permitted
to eliminate the copying inherent in this direct-initialization by
constructing the intermediate result directly into the object being
initialized; see 12.2, 12.8.
简单点说,就是语句T a = v;在当v的类型不同于T(也不是T的派生类)的时候,语义上
要先用v构造一个临时对象T(或者调用自定义转换隐式cast到T类型或者T的派生类型),
然后显式调用copy ctor来构造a
之所以强调语义上,是因为标准说了这个调用可以优化掉,但是语义存在
而强调显式,则说明copy ctor可以为explicit
此外需要强调的就是虽然标准在这里说构造了临时对象T(v)之后(或者是将v隐式转换
为类型T,static_cast<T>(v))再用direct-initialize直接构造a,整个语句总的执行
形式为T a(T(v));(static_cast<T>(v)用C语法也可记为T(v)),但是用T(v)所调用的
ctor只能是T的copy ctor,原因是编译器已经进行了一次自定义的隐式转换T(v),不能
再进行第二次,所以必须调用对T(v)直接参数匹配的ctor,这就是copy ctor
对上面的句子还有一个补充,就是派生类向基类的cast不算自定义隐式转换,是自动发
生的,所以还有一种选择就是T a(U(v));而U是T的派生类!最终调用的仍旧是copy ctor
示例如下
struct T
{
T(){}
T(int){}
operator int(){return 0;}
private:
explicit T(T&){}
};
int main()
{
T a = T(); // 编译通过,相当于隐式调用T a(T());
// 而重载规则选用了T(int),即T a(static_cast<int>(T()));
// 如果T::T(int)声明为explicit,编译出错

T b = 0; // 编译出错,copy ctor不能访问,虽然被优化这个调用,但是语义尚存

return 0;
}

下面再用一个示例补充说明
struct T
{
T(int){}
explicit T(const T&){}
};
void foo(T)
{
}
int main()
{
foo(T(0)); // 编译出错,按照*注2,要求隐式拷贝给foo的参数
// 相当于T t = T(0);

foo(0); // 编译通过,用0构造一个临时对象T(0),然后显式拷贝给foo的参数
// 相当于T t = 0;

return 0;
}
由于函数的按值传参和按值返回都是copy-initialization,所以会有如上的结果差异
回复
taodm 2007-01-05
execptional C++ item42
回复
huangyangman 2007-01-05
同意zhousqy(标准C匪徒)(甩拉,甩拉)
回复
zhousqy 2007-01-05
Foo f = 100;
等於
Foo f(100);
回复
todototry 2007-01-05
分析Foo f = 100这句,应该是先调用Foo(int)来构造临时对象,再调用拷贝构造函数Foo(const Foo &f)来构造f,然后再删除临时对象。
============================================
理论上,是这样的执行
回复
zhousqy 2007-01-05
Foo f = 100;
等於
Foo f(100);
回复
abblly 2007-01-05
------
将Foo(const Foo &f)设为private,则不能编译。
---------
are u sure???
回复
发动态
发帖子
C++ 语言
创建于2007-09-28

5.9w+

社区成员

C++ 语言相关问题讨论,技术干货分享,前沿动态等
申请成为版主
社区公告
暂无公告