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
// (2)
中的参数是按值传递的,因此,当operator()
存在时,临时对象将被销毁,因此a
类的apply
成员将引用垃圾。这对我来说很有意义。但是,如果我们取消注释类sum
中的析构函数定义(请参阅// (1)
),那么结果是正确的,并且最终的sum
对象引用正确的container
。为什么?
如果我们保留析构函数的注释,并将// (2)
更改为通过引用传递,那么一切似乎都可以正常工作。是否因为const引用延长了operator+
返回的临时对象的生存期?如果是这样,为什么apply<A>
构造函数不延长param
的{{1}}对象的生存期? apply类保留对在构造函数中传递的对象的引用。
答案 0 :(得分:3)
// (2)
中的参数是按值传递的,因此,当operator()
存在时,临时对象将被销毁,因此a
类的apply
成员将引用垃圾。这对我来说很有意义。
正确。在
inline apply<A> operator() (A param)
返回的apply<A>
具有对param
的引用,该引用超出了范围,因此您有一个悬空引用,使用它是未定义的行为。
但是,如果我们取消注释类
sum
中的析构函数定义(请参阅// (1)
),那么结果是正确的,并且最后的sum
对象引用了正确的container
。为什么?
再次,未定义的行为。仅仅因为代码不起作用并不意味着就不能。由于您的行为不确定,甚至可以为您提供“正确的”结果。
如果我们保留析构函数的注释,并将
// (2)
更改为通过引用传递,那么一切似乎都可以正常工作。是否因为const引用延长了operator+
返回的临时对象的生存期?
这是因为您已将引用绑定到呼叫站点中的对象。这意味着当函数返回到调用站点时,其引用仍然引用您传递给它的有效对象。这意味着您的引用仍然指向有效的对象,并且您已定义行为。
如果是这样,为什么
apply<A>
构造函数不延长param
的{{1}}对象的生存期? apply类保留对在构造函数中传递的对象的引用。
operator()
仅延长了函数本地临时生存期。
const &
以上是合法的,编译器会将返回值的生存期延长到范围的末尾。具有
{ // 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
对象则被销毁,则该对象将留下一个悬挂的引用。