【问题标题】:How to befriend a templated class's constructor?如何与模板类的构造函数交朋友?
【发布时间】:2011-02-18 21:23:01
【问题描述】:

为什么

class A;
template<typename T> class B
{
private: 
    A* a;

public:  
    B();
};


class A : public B<int>
{
private:    
    friend B<int>::B<int>();
    int x;
};


template<typename T>
B<T>::B()
{
    a = new A;
    a->x = 5;
}

int main() { return 0; }

结果

../src/main.cpp:15:错误:将构造函数用作模板无效
../src/main.cpp:15: 注意:使用'B::B'而不是'B::class B'以限定名称命名构造函数

但将 friend B&lt;int&gt;::B&lt;int&gt;() 更改为 friend B&lt;int&gt;::B() 会导致

../src/main.cpp:15: 错误:没有在类“B”中声明的“void B::B()”成员函数

同时完全删除模板

class A;
class B
{
private:
    A* a;

public:
    B();
};


class A : public B
{
private:
    friend B::B();
    int x;
};


B::B()
{
    a = new A;
    a->x = 5;
}

int main() { return 0; }

编译和执行都很好——尽管我的 IDE 说朋友 B::B() 语法无效?

【问题讨论】:

  • Visual C++ 2008 也不接受 friend B&lt;int&gt;::B(),但 Comeau 接受。
  • Visual C++ 2008 接受friend B&lt;int&gt;::B&lt;int&gt;(),即使禁用了语言扩展。为什么 GCC (4.1) 不接受它?
  • 如果其他人不知道(我不知道),构造函数可以被声明为朋友:open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#263
  • @James:感谢那个链接,我很高兴看到 N4411 包含它。
  • gcc 很高兴与朋友 B::B();

标签: c++ templates gcc constructor friend


【解决方案1】:

根据CWG defect 147 的解决方案(该解决方案已合并到 C++03 中),命名类模板特化的非模板构造函数的正确方法是:

B<int>::B();

而不是

B<int>::B<int>();

如果允许后者,那么当你有一个类模板特化的构造函数模板特化时就会产生歧义:第二个&lt;int&gt; 是用于类模板还是构造函数模板? (有关详细信息,请参阅上面链接的缺陷报告)

因此,将类模板特化的构造函数声明为友元的正确方法是:

friend B<int>::B();

Comeau 4.3.10.1 和 Intel C++ 11.1 都接受这种形式。 Visual C++ 2008 和 Visual C++ 2010 都不接受该表单,但都接受(不正确的)表单 friend B&lt;int&gt;::B&lt;int&gt;();(我将在 Microsoft Connect 上提交缺陷报告)。

gcc 不接受 4.5 之前的任何一种形式。 Bug 5023 是针对 gcc 3.0.2 报告的,但错误报告中请求的解决方案是无效的形式。看来bug 9050 的决议也解决了这个问题,gcc 4.5 接受了正确的形式。 Georg Fritzsche 在对该问题的评论中验证了这一点。

【讨论】:

  • 我向 Microsoft Connect 发布了一份缺陷报告:connect.microsoft.com/VisualStudio/feedback/details/558796。当类模板特化是试图与构造函数交朋友的类的基类时,我只能重现该问题。从代码中删除继承后,Visual C++ 似乎可以正确处理友元声明。如果您能找到更简单的测试用例,请随时将其作为对该缺陷报告的评论发布。
  • 奇怪的是,他们在第 5.1.1/6 条 (FCD) 中留下了一条奇怪的规则,即“使用类名 :: 类名,并且两个类名指的是同一个类,这个符号命名构造函数(12.1)。”。这条规则不仅在 3.4.3.1/2 存在的情况下无用而且与之相矛盾(B&lt;int&gt;::B&lt;int&gt; 将命名任何非模板构造函数)。我的印象是 issue #147 应该删除那个奇怪的规则。我相信第 5 条(关于表达式)无论如何都是此类规则的错误位置(正确的是 3.4)。
  • @Johannes:我认为 §12.1/1(C++03 和 FCD)有同样的问题:“......构造函数的类名后跟参数列表用于声明或定义构造函数。”
【解决方案2】:

在后一种情况下,您的 IDE 将朋友 B::B() 显示为无效语法的原因是什么? IDE 错误。

如果您无法升级,我在 gcc 中为模板案例找到的一种解决方法是将 B() 的实现移至成员函数 void B::init(),并授予其友谊。我敢打赌这也会关闭你的 IDE。

不过,即使你编译它,你也会在尝试实例化 B 时遇到堆栈溢出问题。

【讨论】:

  • 没错,看起来A 不应该从B 继承,这样可以解决无限递归问题。
【解决方案3】:

typedef 没有帮助吗?例如

class A : public B<int>
{
    typedef B<int> Base;   
    friend Base::Base();
    int x;
};

编辑:C++0x 的最终委员会草案在第 3.4.3.1 节 [class.qual] 中包含以下语言:

在构造函数是可接受的查找结果且 nested-name-specifier 指定类 C 的查找中:如果在 之后指定的名称nested-name-specifier,当在 C 中查找时,是 C 的注入类名(第 9 条),或者如果后面指定的名称nested-name-specifieridentifiersimple-template-idtemplate-name 相同em> 在 nested-name-specifier 的最后一个组件中,该名称被认为是命名类 C 的构造函数。

听起来像 nested-name-specifier (Base::) 之后指定的名称 (Base) 与最后一个组件中的 identifier 相同nested-name-specifier,所以这段代码确实命名了一个构造函数。

但我无法将其与第 12.1 节 [class.ctor] 调和:

由于构造函数没有名称,因此在名称查找过程中永远找不到它们

哦,真的吗? 3.4.3.1 中的语言如何再次工作?

typedef-name 不得用作构造函数声明的 declarator-id 中的 class-name

这似乎很清楚,除了第 12.1 节似乎只讨论了构造函数的引入声明,因为第 1 段不包括 nested-name-specifierfriendusing。如果它确实适用于朋友声明,它似乎也禁止friend Base::B();

使用可选的函数说明符序列(7.1.2)后跟构造函数的类名和参数列表的特殊声明符语法用于声明或定义构造函数。

那些函数说明符inlinevirtualexplicit,构造函数无论如何不能是virtual

【讨论】:

  • 不幸的是,不,因为构造函数不能这样命名。它必须是 friend Base::B(),这是正确的,但 Visual C++ 不接受,或者 friend Base::B&lt;int&gt;(),这是不正确的,但 Visual C++ 接受。
  • @James:在您看来,12.1 的第 3 段是仅指与第 1 段一样的引入(类中)声明,还是也包括 friendusing 声明。跨度>
  • 我不认为 §3.4.3.1/2 的引用来自 FCD——在 FCD 中,以“或者如果指定的名称...”开头的第二部分以“在作为成员声明的使用声明中”,因此此处不适用。我目前的想法是§12.1/3 适用于任何时候声明构造函数,包括在朋友声明中。我同意它的措辞方式禁止friend Base::B();。不过,我不确定这是否是意图。我试图找到导致该句子被添加的建议或缺陷,但没有任何运气。
  • 还要注意 Johannes 对我的回答的评论,即 §5.1.1/6(我认为 §12.1/1)直接与 §3.4.3.1/2 相矛盾。今天下午有空的时候我会在 comp.std.c++ 上问这个问题。
  • @James,我认为你是对的,它不是 FCD。这些天找到 FCD 有点困难,我想我发现 Herb Sutter 写了关于 FCD 的博客,并链接到一长串论文,并试图从列表中挑选出标准草案。显然我得到了错误的版本。我认为friend 声明应该与using 声明相同,但不确定它们是否仍在调整不破坏现有代码的内容。
【解决方案4】:

我猜你;在这里使用朋友模板构造函数进入怪癖领域。这在 VS2010 上编译并运行良好,但是当 A 的默认构造函数调用 B 的默认构造函数然后再次实例化 A 时,它会产生堆栈溢出。

【讨论】:

  • 是的,绝对不想尝试实例化我写的那些类,呵呵。请注意我在此处添加的明确的int main() { return 0; },即“不要尝试做任何其他事情”:-)
猜你喜欢
  • 2021-06-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多