【问题标题】:CRTP and lifetime extensionCRTP 和寿命延长
【发布时间】:2019-06-10 15:19:37
【问题描述】:

我的问题是如何使用 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&lt;A&gt; 子对象只存在到B 构造函数之前。

我有两个问题:

1) 为什么?它与结构的第一种情况没有什么不同,每个生命周期在编译时都是已知的,所以我相信编译器应该能够延长所有临时结构的生命周期。

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

【问题讨论】:

  • 通过 const-reference 延长生命周期在这个问题中得到了很好的总结,特别是在您似乎正在驾驶的情况下,const 参考类成员:Does a const reference class member prolong the life of a temporary?。我认为 CRTP 与您遇到的问题没有任何关系。在main 到达b.DifferentGet() 时,来自A{123} 的临时地址已消失
  • 谢谢!好吧,我知道它不是特定于 CRTP,但我想问是否有任何模式(可能特定于 CRTP)可以帮助克服它。
  • 您的第一个代码块无效。不幸的是(也许)C++ 中没有机制可以让您在类中存储对临时对象的引用并延长它的生命周期。
  • 您实际上想要完成什么?知道我们应该能够提出适当的解决方法。
  • 不,它是有效的。看看最后的例子en.cppreference.com/w/cpp/language/…

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


【解决方案1】:

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,那么您猜对了,需要延长寿命。如果 implementation 是 2,那么我们就不必要地扩展了一个不再被引用的临时对象。

语言设计者选择不延长这种临时性,可能是为了避免不必要地延长临时性的寿命。因此,实现 1 以及您的 CRTP 示例都是错误的。

简而言之:编译器只能将临时对象的生命周期延长到该临时对象直接绑定到的引用的生命周期。编译器无法知道函数中的引用将做什么。它无法知道参数与成员有关。这些只有在编译构造函数时才知道 - 在编译对构造函数的调用时不知道。


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

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

【讨论】:

  • 这是一个很好的答案,谢谢!那么原因是成员初始化器列表没有义务在标题中?那就可惜了。希望有一天他们会想出一些特殊类型的构造函数,可以涵盖这种情况
  • 不过,我不认为您想出了一个好的解决方案。问题不在const int&amp;——这部分没有问题。真正的问题是const Gettable&lt;T&gt;&amp;,我还没有找到解决办法。
【解决方案2】:

我相信最通用的解决方案是这样的。这种方式甚至适用于多级继承。

#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;
}

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

【讨论】:

    猜你喜欢
    • 2017-07-15
    • 1970-01-01
    • 1970-01-01
    • 2013-01-02
    • 2011-06-07
    • 2012-06-03
    • 1970-01-01
    • 1970-01-01
    • 2011-11-26
    相关资源
    最近更新 更多