我的问题是如何使生存期延长与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/

10-09 22:46