c++ - 声明析构函数会影响对象的生命周期?
问题描述
15 年后回到 C++ 并玩弄“发现现代 C++”一书中描述的表达式模板概念,我遇到了一个我无法解释的行为(尽管当时我的 C++ 知识非常基础,所以我希望这个是显而易见的)。
这是最小的例子(我知道它很长,但这是我能做的最好的说明问题):
#include <iostream>
#include <iomanip>
template <typename T>
class container {
private:
T data;
template<typename Src>
void copy_from(Src& that) {
data = that.get();
}
public:
using value_type = T;
inline T get() const {
auto p= data;
return p;
}
void set(T v) {
data = v;
}
template<typename Src>
container& operator=(const Src& that) {
copy_from(that);
return *this;
}
template<typename Src>
container(const Src& that){
copy_from(that);
}
container() = default;
friend std::ostream& operator <<(std::ostream& s, container<T> const & matrix) {
s << std::endl << std::fixed << std::setprecision(8) << matrix.data <<std::endl;
return s;
}
};
template <typename A1, typename A2>
class sum {
using mytype = sum<A1, A2>;
public:
sum(const A1 & a1, const A2 & a2):
a1(a1), a2(a2) {
std::cout <<"constructing sum ("<<(long)this<<") with a1 = " << (long)&a1 << " and a2 = " <<(long)&a2 << std::endl;
}
// (1)
// ~sum() {}
using value_type = std::common_type_t <typename A1::value_type, typename A2::value_type>;
inline value_type get() const {
std::cout <<"getting elem from sum ("<<(long)this<<") with a1 = " << (long)&a1 << " and a2 = " <<(long)&a2 << std::endl;
auto x = a1.get();
auto y = a2.get();
auto p = x + y;
return p;
}
void print() {
std::cout <<"I'm a sum ("<<(long)this<<") with a1 = " << (long)&a1 << " and a2 = " <<(long)&a2 << std::endl;
}
private:
const A1 &a1;
const A2 &a2;
};
template <typename A1, typename A2>
sum<A1, A2> inline operator+ (const A1& a1, const A2& a2) {
return {a1, a2};
}
template <typename A>
class apply {
public:
using value_type = typename A::value_type;
using function_type = std::function<value_type(value_type)>;
apply (const A& a, const function_type & f):
a(a), f(f) {std::cout <<"constructing apply ("<<(long)this<<") with a = " << (long)&a<< std::endl; }
inline value_type get() const {
std::cout <<"address of apply's member obj is " << (long)&a << ", type is " <<typeid(a).name() << std::endl;
auto p = f(a.get());
return p;
}
private:
const A &a;
const function_type & f;
};
template<typename T>
class applicator {
public:
using value_type = T;
using function_type = std::function<value_type(value_type)>;
applicator( const function_type & f): f(f) { }
template<typename A>
// (2)
inline apply<A> operator() (A param) {
std::cout <<"address of () param is " << (long)¶m << ", type is " <<typeid(param).name() <<": ";
param.print();
apply<A> op { param, f };
return op;
}
private:
const function_type & f;
};
double square(double x) {
return x*x;
}
int main() {
std::cout << "--- Creating variable" << std::endl;
container<double> W;
std::cout << W;
std::cout << "--- Setting values in the variable" << std::endl;
W.set(4);
std::function<double(double)> my_fun = square;
applicator sq { my_fun };
std::cout << "decltype(W) is_trivially_copyable? " << std::is_trivially_copyable_v<decltype(W)> << std::endl;
std::cout << "decltype(W+W) is_trivially_copyable? " << std::is_trivially_copyable_v<decltype(W+W)> << std::endl;
std::cout << "decltype(sq(W+W)) is_trivially_copyable? " << std::is_trivially_copyable_v<decltype(sq(W+W))> << std::endl;
std::cout << "--- Performing function on addition" << std::endl;
std::cout << std::hex;
auto r = sq(W+W);
std::cout << "Created var r with address " <<(long)&r<<", type: " <<typeid(r).name() <<std::endl;
std::cout << "--- Copying to container and printing out results" << std::endl;
std::cout << r << std::endl;
return 0;
}
这是输出:
--- Creating variable
0.00000000
--- Setting values in the variable
decltype(W) is_trivially_copyable? 1
decltype(W+W) is_trivially_copyable? 1
decltype(sq(W+W)) is_trivially_copyable? 1
--- Performing function on addition
constructing sum (7ffeefbff3a0) with a1 = 7ffeefbff508 and a2 = 7ffeefbff508
address of () param is 7ffeefbff388, type is 3sumI9containerIdES1_E: I'm a sum (7ffeefbff388) with a1 = 7ffeefbff508 and a2 = 7ffeefbff508
constructing apply (7ffeefbff398) with a = 7ffeefbff388
Created var r with address 7ffeefbff4e0, type: 5applyI3sumI9containerIdES2_EE
--- Copying to container and printing out results
address of apply's member obj is 7ffeefbff388, type is 3sumI9containerIdES1_E
getting elem from sum (7ffeefbff388) with a1 = 7ffeefbff4c8 and a2 = 7ffeefbff3b0
0.00000000
in 的参数// (2)
是按值传递的,所以临时对象在operator()
存在时会被销毁,因此类的a
成员apply
引用垃圾。这对我来说很有意义。但是,如果我们取消注释类中的析构函数定义sum
(请参阅参考资料// (1)
),那么结果是正确的并且最终sum
对象引用了正确container
的 s。为什么?
如果我们保留析构函数的注释并更改// (2)
为通过引用传递,一切似乎都正常工作。是不是因为 const 引用延长了返回的临时对象的生命周期operator+
?如果是这样,为什么apply<A>
构造函数不延长param
对象的生命周期operator()
?apply 类保持对构造函数中传递的对象的引用。
解决方案
in 的参数
// (2)
是按值传递的,所以临时对象在operator()
存在时会被销毁,因此类的a
成员apply
引用垃圾。这对我来说很有意义。
正确的。在
inline apply<A> operator() (A param)
返回apply<A>
的引用param
已超出范围,因此您有一个悬空引用并且使用它是未定义的行为。
但是,如果我们取消注释类中的析构函数定义
sum
(请参阅参考资料// (1)
),那么结果是正确的并且最终sum
对象引用了正确container
的 s。为什么?
再次,未定义的行为。仅仅因为代码不应该工作并不意味着它不能。由于您有未定义的行为,因此甚至可以为您提供“正确”的结果。
如果我们保留析构函数的注释并更改
// (2)
为通过引用传递,一切似乎都正常工作。是不是因为 const 引用延长了返回的临时对象的生命周期operator+
?
这是因为您已经绑定了对来自调用站点的对象的引用。这意味着当函数返回调用站点时,它的引用仍然指向您传递给它的有效对象。这意味着您的引用仍然指向一个有效的对象,并且您已经定义了行为。
如果是这样,为什么
apply<A>
构造函数不延长param
对象的生命周期operator()
?apply 类保持对构造函数中传递的对象的引用。
Aconst &
仅延长函数本地临时的生命周期。
{ // start of some scope
const int& foo = function_that_returns_temporary();
} // end of some scope
以上是合法的,编译器会将返回值的生命周期延长到作用域的末尾。有
struct Foo
{
const int& bar
Foo(const int& ref) : bar(ref)
};
不延长ref
所指的寿命。如果在用它创建的对象被销毁ref
之前,任何引用超出范围,那么该对象将留下一个悬空引用。Foo
推荐阅读
- java - ClickListener touchDragged pointer always zero
- facebook - Expo React Native Simulator 错误:Facebook SDK 尚未初始化
- ios - 使用未声明类型的 UIUserActivity
- flutter - 需要在 TabBarView 中展开,但高度错误
- node.js - Angular 构建 - 未找到模块:错误:无法解析“控制台”
- typescript - 如何使用 TS 覆盖全局变量定义
- npm - 使用 Ansible npm 包安装 Angular
- azure-cognitive-search - Azure 搜索:Blob 元数据字段值未出现在索引数据中
- python - 如何使用异步循环遍历列表并调用列表对象自己的函数
- html - 我想让图像与文本中的文本左对齐