为什么结构需要朋友功能?

时间:2018-09-17 13:41:07

标签: c++

我正在尝试构建一个program,其来源是我从互联网上下载的。尝试编译时,出现错误消息

friend declaration specifying a default argument must be the only declaration

这是有问题的代码:

typedef int Var;
struct Lit {
    int     x;
    // Use this as a constructor:
    friend Lit mkLit(Var var, bool sign = false);
    bool operator == (Lit p) const { return x == p.x; }
    bool operator != (Lit p) const { return x != p.x; }
    bool operator <  (Lit p) const { return x < p.x;  } 
inline  Lit  mkLit(Var var, bool sign) { Lit p; p.x = var + var + (int)sign; return p; }

我发现了几个可以解决此问题的问题,但问题相当深刻,我不太理解这些解释。 answer to this question表示我可以通过以下方法解决问题:将mkLit的内联定义移到结构之前,从好友函数声明中删除默认参数,然后将其移至内联定义。这是正确的吗?

但更简单地说,我不明白为什么结构需要朋友功能,因为它的成员无论如何都是公共的。对this question的公认答案给出了一个关于参数依赖查找的答案(我的无知使我无法理解),但是在这种情况下我看不到它适用。

仅删除好友函数声明并将默认参数移至内联函数定义是否有任何弊端?如果是这样,您能举一个简单的例子吗?

3 个答案:

答案 0 :(得分:5)

  

但更简单地说,我不明白为什么结构需要朋友   功能,因为它的成员还是公开的。

这是一个误会。 C ++中没有结构和类,但是C ++仅具有可以用关键字structclass之一声明的类。唯一的区别是默认访问权限,即以下两个相同:

struct foo : private bar {
    int x;
private:
    int y;
};

class相同:

class foo : bar {
     int y;
public:
     int x;
};

使用classstruct声明类纯粹是约定俗成的问题。因此,您的问题翻译为“为什么一个班级需要一个朋友功能?”答案是:允许朋友访问私有字段。

question you linked是关于内联定义朋友功能而不是直接声明它的,即

struct foo { 
    friend void foofriend() { /*put implementation here*/ }
};

vs

struct foo {
    friend void foofriend();
};

void foofriend() { /*put implementation here*/ }

这确实与ADL有关(我也无法解释),并且与朋友有什么用的问题正交。

答案 1 :(得分:1)

花了一段时间才知道,但最终我发现了。

因此,我忽略了误导性的标题(已经answered by user463035818恕我直言了),并专注于

  

friend declaration specifying a default argument must be the only declaration

引起了我的注意。

因此,我不会重复关于friend和公众成员的讨论,因为我认为此错误涉及另一个问题。

首先,我在coliru上尝试了OP的示例(有一点点修复和main()的测试):

#include <iostream>

typedef int Var;
struct Lit {
    int     x;
    // Use this as a constructor:
    friend Lit mkLit(Var var, bool sign = false);
    bool operator == (Lit p) const { return x == p.x; }
    bool operator != (Lit p) const { return x != p.x; }
    bool operator <  (Lit p) const { return x < p.x;  }
};
inline  Lit  mkLit(Var var, bool sign) { Lit p; p.x = var + var + (int)sign; return p; }

int main()
{
  Lit lit2 = mkLit(123, false);
  std::cout << "lit2.x: " << lit2.x << '\n';
  return 0;
}

输出:

g++ (GCC) 8.1.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

lit2.x: 246

Live Demo on coliru

啊,是的。运行正常。

然后我这次在clang HEAD 8.0.0上对wandbox进行了同样的操作:

Start

prog.cc:7:16: error: friend declaration specifying a default argument must be a definition
    friend Lit mkLit(Var var, bool sign = false);
               ^
prog.cc:12:14: error: friend declaration specifying a default argument must be the only declaration
inline  Lit  mkLit(Var var, bool sign) { Lit p; p.x = var + var + (int)sign; return p; }
             ^
prog.cc:7:16: note: previous declaration is here
    friend Lit mkLit(Var var, bool sign = false);
               ^
2 errors generated.

1

Finish

Live Demo on wandbox

我们在这里。

因此,我试图了解clang的抱怨。

最后,我发现clang找到了Lit Lit::mkLit(),但与以后定义的Lit mkLit()不匹配。

这可以通过在mkLit()之前插入的struct Lit的原型来解决:

typedef int Var;

Lit mkLit(Var var, bool sign = false);

struct Lit {
    int     x;
    // Use this as a constructor:
    friend Lit mkLit(Var var, bool sign);
    bool operator == (Lit p) const { return x == p.x; }
    bool operator != (Lit p) const { return x != p.x; }
    bool operator <  (Lit p) const { return x < p.x;  }
};

现在,我遇到了一个新问题:Lit的原型定义时尚未知道mkLit()。 因此,我需要对Lit进行前向声明,并以:

结尾
#include <iostream>

typedef int Var;

struct Lit;
Lit mkLit(Var var, bool sign = false);

struct Lit {
    int     x;
    // Use this as a constructor:
    friend Lit mkLit(Var var, bool sign);
    bool operator == (Lit p) const { return x == p.x; }
    bool operator != (Lit p) const { return x != p.x; }
    bool operator <  (Lit p) const { return x < p.x;  }
};
inline  Lit  mkLit(Var var, bool sign) { Lit p; p.x = var + var + (int)sign; return p; }

int main()
{
  Lit lit2 = mkLit(123, false);
  std::cout << "lit2.x: " << lit2.x << '\n';
  return 0;
}

输出:

Start

lit2.x: 246

0

Finish

Live Demo on wandbox

问题已解决。

我必须承认,我不确定g++是否接受原始版本(尽管不应该接受)或clang是否拒绝原始版本(虽然不应该)。 我的肚子感觉倾向于前者(即clang是正确的)...


再次思考g++中会发生什么之后,我得出的结论是它可以执行以下操作:

  1. 接受friend Lit Lit::mkLit()(从未使用过)

  2. 定义另一个Lit mkLit()

要找出我将“ struct Lit转换为class Lit的情况,这也会在g++中产生一个错误:

#include <iostream>

typedef int Var;
class Lit {
    int     x;
    // Use this as a constructor:
    friend Lit mkLit(Var var, bool sign = false);
    bool operator == (Lit p) const { return x == p.x; }
    bool operator != (Lit p) const { return x != p.x; }
    bool operator <  (Lit p) const { return x < p.x;  }
};
inline  Lit  mkLit(Var var, bool sign) { Lit p; p.x = var + var + (int)sign; return p; }

int main()
{
  Lit lit2 = mkLit(123, false);
  std::cout << "lit2.x: " << lit2.x << '\n';
  return 0;
}

输出:

g++ (GCC) 8.1.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

main.cpp: In function 'int main()':
main.cpp:17:35: error: 'int Lit::x' is private within this context
   std::cout << "lit2.x: " << lit2.x << '\n';

Live Demo on coliru

因此,OP的原始版本仅在g++中起作用,因为带有公共struct Lit的{​​{1}}根本不需要int Lit::x

现在,我有点困惑。 friend Lit::mkLit()g++中哪一个是正确的?我不知道。

答案 2 :(得分:0)

正如前面提到的,structclass相同,只是默认情况下其成员为public。它可以有私人成员。

我在这里猜测,但是我认为使它成为朋友工厂功能的想法是,库的设计者保留了初始化以后添加的任何私有成员的权利。

要回答您的问题:

  • 将默认参数移至内联函数似乎可行。
  • mkLit之前移动struct是一个问题,因为它返回了Lit,并且尚未定义Lit(因为它现在出现在{{ 1}})。
  • 删除mkLit内的friend声明将起作用,因为struct目前没有私有成员。如果您合并了更改后的原始库的新版本,则会添加私有成员(或默认情况下通过将文本struct更改为struct来使成员私有),那么它将停止工作。

(注意:我将您的代码粘贴到http://godbolt.org中,并使用不同的编译器尝试了各种排列)