我的问题是如何使生存期延长与CRTP一起使用。例如,以下代码是完全有效的:
struct A {
const int& ref;
};
struct B {
const A& a;
};
int main() {
B b{{123}};
return b.a.ref;
}
其CRTPed版本不是:
template <class DerivedT>
class Gettable {
public:
int Get() const {
return static_cast<const DerivedT*>(this)->GetImpl();
}
};
class A : public Gettable<A> {
friend class Gettable<A>;
public:
A(int r) : ref{r}{}
private:
int GetImpl() const {
return ref;
}
const int& ref;
};
template <class T>
class B {
public:
B(const Gettable<T>& gettable) : get_{gettable}{}
int DifferentGet() const {
return get_.Get();
}
private:
const Gettable<T>& get_;
};
int main() {
B b{A{123}};
return b.DifferentGet();
}
问题在于原始的
A
及其Gettable<A>
子对象仅存在到B
构造函数之前。我有两个问题:
1)为什么?与第一种结构没有什么不同,每个生存期都在编译时知道,因此我相信编译器应该能够延长所有临时生存期。
2)有什么好的方法可以解决这个问题?
最佳答案
因为涉及到一个函数-构造函数。临时不是直接绑定(bind)到成员,而是直接绑定(bind)到函数的参数-生存期会延长到函数结束,而不会超出调用函数的完整表达式。
那不一样。聚集初始化没有涉及构造函数。在那种情况下,编译器知道该成员的生存期,并且知道该成员是用临时初始化的。寿命延长规则适用。
考虑以下示例:
struct foo {};
struct bar {
bar(const foo& farg);
const foo& fmem;
};
bar b({});
临时文件的生存期是否应该延长到
b
的生存期?该标准说,事实并非如此。您似乎在争论应该这样做。考虑以下构造函数的可能实现:
bar::bar(const foo& farg) : fmem{farg} {} // 1
foo fanother;
bar::bar(const foo& farg) : fmem{fanother} {} // 2
如果实现恰好是1,那么您猜对了,需要延长使用寿命。如果实现为2,则我们不必要地扩展了不再引用的临时目录。
语言设计师选择不延长此类临时期限,可能是为了避免不必要地延长临时人员的寿命。因此,实现1和您的CRTP示例都是错误的。
简而言之:编译器只能将临时项的生存期延长到该临时项直接绑定(bind)到的引用的生存期。编译器无法知道使用函数中的引用将要执行的操作。它不知道参数与成员有关。这些仅在编译构造函数时才知道,而不是在编译对构造函数的调用时才知道。
使用
int*
或std::reference_wrapper<int>
作为构造函数参数。前者更为简洁,但后者具有不具有null表示的便利属性。这些应该使得更难于意外绑定(bind)悬挂的引用。无论如何,请仔细说明在调用Get
时所引用的对象必须仍然有效。关于c++ - CRTP和生命周期延长,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56529140/