模板类的嵌套类 - 互操作性和可见性

Nested class of a template class - interoperability and visibility

提问人:Šimon Hrabec 提问时间:10/23/2023 更新时间:10/23/2023 访问量:81

问:

我有一个带有嵌套类的模板类 - 我用它来隐藏实现细节。我可能有不同的类实例化。 可以保存指针,我可能需要在 2 个不同的实例化中使用这些指针进行操作。在这些情况下,我可能会得到 2 个不同的 Inner 类 - 和 .从编译器的角度来看,它们是不同的类型,并且不兼容。一个非工作的例子:A<T>InnerAAInnerA<T1>::InnerA<T2>::Inner

template<typename T>
class A {
    template <typename T2> friend class A;

    struct Inner {
        Inner(int64_t timestamp) : timestamp(timestamp) {}
        int64_t timestamp;
    };

    Inner* inner_pointer;
    T val;

public:
    template<typename U>
    A(const A<U> other, const T& val) : inner_pointer(other.inner_pointer), val(val) {}

    A(const T& val, int64_t time_now) : inner_pointer(new Inner(time_now)), val(val) {}
};

int main(int argc, char const *argv[]){
    A first('a', 123);
    A second(first, 0.2);
    return 0;
}

解决这个问题的一种方法可能是使用 ,但这很丑陋,我猜也是 UB。reinterpret_cast<Inner*>(other.inner_pointer)

另一种方法是不创建嵌套类,而是创建一个单独的类:Inner

struct Inner {
    Inner(int64_t timestamp) : timestamp(timestamp) {}
    int64_t timestamp;
};

template<typename T>
class A {
    template <typename T2> friend class A;

    Inner* inner_pointer;
    T val;

public:
    template<typename U>
    A(const A<U> other, const T& val) : inner_pointer(other.inner_pointer), val(val) {}

    A(const T& val, int64_t time_now) : inner_pointer(new Inner(time_now)), val(val) {}
};

这解决了问题,但它使使用该类的任何人都可以看到该类,因为它是需要包含的头文件的一部分,并且它还污染了范围。InnerA

我猜 STL 实现使用保留的名称(以下划线开头)来解决这个问题。有没有一种简单/优雅的方法如何在不暴露类的情况下实现这一点?Inner

C++ 模板 类型 实例化

评论

1赞 Alan Birtles 10/23/2023
让它成为一个单独的类似乎是正确的方法。一种典型的方法是将 toys 类放在 or 命名空间中,以指示它不适合外部使用detailimpl
1赞 JaMiT 10/23/2023
“这也污染了范围”——好主意。如何将名称移动到另一个范围?-- “我猜 STL 实现使用保留的名称来解决这个问题” -- 这是一种方式。这也是 detail 命名空间通常用于的用途
1赞 user17732522 10/23/2023
"我猜也是UB“:是的,当然。
0赞 user17732522 10/23/2023
"并且不兼容“:类型兼容性是 C++ 中未使用的 C 概念。
1赞 463035818_is_not_an_ai 10/23/2023
人们可以在字里行间读出它,但你从来没有提到你真的想要成为同一个类型。A<T>::InnerA<U>::Inner

答:

2赞 JaMiT 10/23/2023 #1

下面是一个基于假设的技巧,该假设不是有效的实例化。(如果这是简化的产物,还有其他方法可以获得类似的效果。A<void>

首先,声明(而不是定义)模板,并将 with 定义为嵌套类型。更一般地说,您可以将所有常用功能(如 inner_pointer 成员)移动到此专用化。这开启了将公共代码(包括 Internal 的定义)从头文件移出到源文件的可能性。A<void>Inner

#include <cstdint>

// Declare the template.
template<typename T>
class A;

// Define A<void>
template<>
class A<void> {
    template <typename T2> friend class A;

    struct Inner {
        Inner(int64_t timestamp) : timestamp(timestamp) {}
        int64_t timestamp;
    };

    // Private constructor prevents the use outside the A template.
    A() = default;
    // Protected (or private) destructor allows safe polymorphism without virtual functions.
    ~A() = default;
};

接下来,定义常规模板,用作基类型。A<void>

template<typename T>
class A : private A<void> {
    template <typename T2> friend class A;

    Inner* inner_pointer;
    T val;

public:
    template<typename U>
    A(const A<U> other, const T& val) : inner_pointer(other.inner_pointer), val(val) {}

    A(const T& val, int64_t time_now) : inner_pointer(new Inner(time_now)), val(val) {}
};

现在,所有模板实例都使用相同的类型。Inner

这样做的缺点是,我们并没有真正改变问题;使用该模板的任何人都可以看到该类型。但是,它不可用(因为一切都是私有的),并且由于没有引入新的标识符,我们已经消除了命名空间污染。A<void>A