CRTP和有效期延长

时间:2019-06-10 15:19:37

标签: c++ c++17 crtp temporary-objects

我的问题是如何使CRTP延长生命周期。例如,以下代码是完全有效的:

struct A {
    const int& ref;
};

struct B {
    const A& a;
};

int main() {
    B b{{123}};
    return b.a.ref;
}

其CRTP版本不是:

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)有什么好的方法可以解决这个问题?

2 个答案:

答案 0 :(得分:2)

  

1)为什么?

因为其中涉及一个函数-构造函数。临时不是直接绑定到成员,而是直接绑定到函数的参数-生存期会延长到函数结束,并且不会超出调用函数的完整表达式。

  

与第一种结构没有什么不同

是不同的。聚集初始化没有涉及构造函数。在这种情况下,编译器知道该成员的生存期,并且知道该成员是用临时初始化的。寿命延长规则适用。

  

所以我相信编译器应该能够延长所有临时对象的寿命。

考虑以下示例:

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示例都是错误的。

简而言之:编译器只能将临时项的生存期延长到该临时项直接绑定到的引用的生存期。编译器无法知道使用函数中的引用将要执行的操作。它不知道参数与成员有关。这些仅在编译构造函数时才知道,而不是在编译对构造函数的调用时才知道。


  

2)有什么好的方法可以解决这个问题?

使用int*std::reference_wrapper<int>作为构造函数参数。前者更为简洁,但后者具有不具有null表示的便利属性。这些应该使得更难于意外地绑定悬挂的参考。无论如何,请仔细记录在调用Get时所引用的对象必须仍然有效。

答案 1 :(得分:0)

我认为最通用的解决方案是这样的。这样,它甚至可以用于多级继承。

#include <iostream>
#include <utility>
#include <type_traits>

struct NullType {};

// Helper class for casting
template <class Derived>
class DerivedCaster {
protected:
    Derived* GetDerived() {
        return static_cast<Derived*>(this);
    }

    const Derived* GetDerived() const {
        return static_cast<const Derived*>(this);
    }
};

// Matches the predicate against the types and remembers the first 
// satisfying argument
template <template <class T> class Predicate, class... Args>
struct FindFirstMatching {
    using Type = ... ; // default NullType
    static const bool has_match = ... ;
};

// Structure which gets the deepest class from CRTP inheritance chain
// by looking at the instantiated parent class template
template<typename T>
struct GetDeepest
{
    using Type = T;
};

template<template<class...> class DT, class... T>
struct GetDeepest<DT<T...>>
{
    template <class CLS>
    struct Predicate {
    static const bool value = std::is_base_of<DT<T...>, CLS>::value;
    };

    static const bool HasCRTPDerived = FindFirstMatching<Predicate, T...>::has_match;
    using DerivedT = typename FindFirstMatching<Predicate, T...>::Type;

    using Type = std::conditional_t<HasCRTPDerived, typename GetDeepest<DerivedT>::Type, DT<T...>>;
};

// First abstract class
template <class DerivedT>
class Gettable : public DerivedCaster<DerivedT> {
public:
    int Get() const {
        return DerivedCaster<DerivedT>::GetDerived()->GetImpl();
    }
};

// Second abstract class
template <class DerivedT>
class Incrementable : public DerivedCaster<DerivedT>,
              public Gettable<Incrementable<DerivedT>> {
    friend class Gettable<Incrementable<DerivedT>>;
public:
    int Increment() const {
        return ++(this->Get());
    }

private:
    int GetImpl() const {
        return DerivedCaster<DerivedT>::GetDerived()->GetImpl() + 100;
    }
};

// non-abstract class
class A : public Incrementable<A> {
    friend class Incrementable<A>;
public:
    A(int r) : ref_{r}{}

private:
    int GetImpl() const {
        return ref_;
    }

    int ref_;
};

// Helper to get the copy of the underlying non-abstract class
template <class T>
auto GetDeepestLevelCopy(const T& arg) {
    return static_cast<const typename GetDeepest<T>::Type&>(arg);
}

// Some other class which wants a copy
template <class T>
class B {
public:
    B(const Gettable<T>& gettable) : get_{GetDeepestLevelCopy(gettable)}{}
    int DifferentGet() const {
        return get_.Get();
    }

private:
    typename GetDeepest<Gettable<T>>::Type get_;
};

int main() {
    static_assert(std::is_same_v<GetDeepest<Gettable<Incrementable<A>>>::Type, A>);
    static_assert(std::is_same_v<decltype(GetDeepestLevelCopy(std::declval<Gettable<Incrementable<A>>>())), A>);

    B b{A{123}};
    std::cout << b.DifferentGet() << "\n";
    // prints 223
    return 0;
}

这看起来很可怕,但我不知道是否有更好的解决方案。