使一个模板参数成为友元?

49

例子:

template<class T>
class Base {
public:
    Base();
    friend class T;
};

现在这个不起作用...有没有办法做到这一点?
我实际上正在尝试创建一个类似于这样的通用类密封器:
class ClassSealer {
private:
   friend class Sealed;
   ClassSealer() {}
};
class Sealed : private virtual ClassSealer
{ 
   // ...
};
class FailsToDerive : public Sealed
{
   // Cannot be instantiated
};

我在某个网站上找到了这个例子,但是我现在找不到它了... (这里)

我知道有其他方法可以做到这一点,但现在我很好奇你是否真的可以做到这样。

3个回答

50

标准中明确禁止这样做,即使某些VisualStudio版本允许使用。

C++标准7.1.5.3详细说明了类型说明符的概念,第2段

3.4.4描述了标识符在详细类型说明符中的查找过程。如果标识符解析为类名或枚举名,则详细类型说明符将以与简单类型说明符引入其类型名相同的方式引入其声明。如果标识符解析为typedef名称或模板类型参数,则详细类型说明符是不良的。[注意:这意味着,在具有模板类型参数T的类模板内,声明“friend class T;”是不良的。]

我将上面的代码识别为封闭(禁止扩展)类的模式。还有另一种解决方案,它并没有真正阻止对类的扩展,但会标记意外地从类进行扩展。如在ADOBE源代码库中所示:

namespace adobe { namespace implementation {
template <class T>
class final
{
protected:
   final() {}
};
}}
#define ADOBE_FINAL( X ) private virtual adobe::implementation::final<T>

使用方法:

class Sealed : ADOBE_FINAL( Sealed )
{//...
};

虽然它允许扩展,但需要强制实现:
class SealBreaker : public Sealed, ADOBE_FINAL( Sealed )
{
public:
   SealBreaker() : adobe::implementation::final<Sealed>(), Sealed() {}
};

这将防止用户错误执行它。

编辑

即将到来的C++11标准允许您使用稍有不同的语法来友元类型参数:

template <typename T>
class A {
   // friend class T; // still incorrect: elaborate type specifier
   friend T;          // correct: simple specifier, note lack of "class"
};

然而,C++11允许使用关键字“final”,例如:class X final{...}(或者您可以将单个虚函数设置为final)。无论如何,我已经尝试了上面的代码(“friend T;”),在没有-std=c++11标志的情况下,它可以正常编译。 - blue scorpion
在我看来,截至2023年,你的补充说明“即将到来的C++11标准确实允许…”应该放在你的回答中首先提到。否则很容易被忽略。 - Elmar Zander

23
我找到了一种简单的技巧来声明模板参数为友元:
template < typename T>
struct type_wrapper 
{ 
   typedef T type; 
}; 


template < typename T> class foo 
{ 
  friend class type_wrapper < T>::type 
};   // type_wrapper< T>::type == T

但是我不知道这如何有助于定义类密封器的另一个版本。


这个符合标准吗?虽然很好用,感谢你的提示! - zennehoy
看起来对我来说符合标准,但我不是标准专家。不错的发现! - onitake
4
不太对...clang 给我以下错误:错误:详细说明的类型引用了一个typedef。友元类TypeWrapper<T>::type; - Viren

1

你真的需要这样做吗? 如果你想防止别人从你的类派生,只需添加注释并使析构函数非虚拟即可。


有时候,最好的技术答案根本不是技术方面的。 - David Rodríguez - dribeas
2
当然可以,但如果在编译时可以检测到非法使用,那不是更好吗?这与使用 assert() 而不是注释的原理相同 - 你不认为 assert() 很有用吗? - j_random_hacker
2
有时候你就是不能将析构函数设置为非虚函数,因为它可能有一个基类,而该基类的析构函数是虚函数。 - Armen Tsirunyan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接