复制含虚拟基类的对象致shared_ptr持对象丢失,程序是否合法?
代码重现
#include <iostream> #include <memory> struct A { std::shared_ptr<int> u{ new int }; }; struct B : virtual A {}; struct C : virtual A {}; struct D : B, C {}; int main() { D d; d = D( d ); std::cout << d.u; }
核心问题拆解
1. std::cout << d.u的输出含义
C++标准库并未为std::shared_ptr重载operator<<,编译器会尝试隐式转换:
- C++11及之后,
std::shared_ptr的operator bool()是显式的,理论上该语句会编译失败;但部分旧编译器或非标准实现允许隐式转换为bool,此时输出0表示d.u为空(get() == nullptr)。 - 若编译器将其解释为输出指针地址,
0直接表示d.u指向空。
2. d = D(d)的行为差异(依赖C++版本)
C++17及以后
C++17明确规定赋值运算符的右侧表达式先于左侧求值,因此流程是确定的:
- 先构造临时对象
D(d):虚拟基类A从d的A子对象拷贝构造,shared_ptr引用计数从1变为2。 - 默认生成的
D赋值运算符会对虚拟基类A执行一次赋值(标准强制要求),临时对象的u拷贝给d的u,引用计数变为3。 - 临时对象销毁后,
u的引用计数降至2,d.u仍有效,正常情况下输出不应为0。
C++17之前
C++17未规定赋值运算符左右操作数的求值顺序,因此d = D(d)属于未定义行为:
- 编译器可能先修改
d(赋值操作),再读取d构造临时对象,导致拷贝构造的是已被破坏的d,最终造成d.u为空,输出0只是未定义行为的一种可能结果。
结论
- 在C++17及以后,程序不存在未定义行为,标准也不要求输出0(正常运行时
d.u应有效,输出非0值)。 - 在C++17之前,
d = D(d)属于未定义行为,输出0是未定义行为的表现之一,并非标准强制要求。
内容的提问来源于stack exchange,提问作者Fedor
相关产品推荐
相关产品推荐

