C++不让我交朋友

32

我有两个类,Mesh和MeshList。我想让MeshList拥有一个可以改变Mesh私有成员的函数。但是它无法编译,我不知道为什么。这是我的代码。

Mesh.h

#ifndef _MESH_H
#define _MESH_H

#include "MeshList.h"
#include <iostream>

class Mesh
{
private:
    unsigned int vboHandle_;
    friend void MeshList::UpdateVBOHandle();
public:
    inline void Out() {std::cout << vboHandle_;}
};
#endif

Mesh.cpp

#include "Mesh.h"

MeshList.h

#ifndef _MESH_LIST_H
#define _MESH_LIST_H

#include "Mesh.h"


class MeshList
{

public:
    Mesh *mesh; //Line 11 Error
    void UpdateVBOHandle();
};
#endif

MeshList.cpp

#include "MeshList.h"

void MeshList::UpdateVBOHandle()
{
    *mesh->vboHandle_ = 4;
}

我遇到了以下错误:

MeshList.h(第11行)

  • 错误 C2143:缺少“;”(分号)
  • 错误 C4430:缺少类型说明符 - 假定为 int。注意:C++ 不支持默认 int
  • 错误 C4430:缺少类型说明符 - 假定为 int。注意:C++ 不支持默认 int

  • mesh.h(第11行):错误 C2653:“MeshList”不是类或命名空间名称

  • meshlist.cpp(第5行):错误 C2248:“Mesh::vboHandle_”无法访问在类“Mesh”中声明的私有成员
  • mesh.h(第10行):请参见“Mesh::vboHandle_”的声明
  • mesh.h(第8行):请参见“Mesh”的声明
  • meshlist.cpp(第5行):错误 C2100:非法的间接引用

42
你需要多出去走走。 - Mark Ransom
5
请尝试使用 class Meshlist; 替代 #include "MeshList.h" - chris
@Matthieu:简单浏览一下,看起来这是一个非常不错的开始。 - Mooing Duck
1
@MooingDuck:是的,但由于 OP 试图只与“MeshList”的一个方法交朋友,所以前置声明行不通。 - Matthieu M.
我在点赞@BenjaminLindley的评论之前甚至没有读过这个问题,现在我被“仅凭标题就加一票”的陷阱所困住了。 - Shep
这是专门为标题制作的,你骗子。 - user155407
4个回答

9
当您编译Mesh.cpp时,它会包含Mesh.h,而Mesh.h又包含MeshList.hMeshList.h开始包含Mesh.h,但在早期停止,因为_MESH_H现已定义。然后(回到MeshList.h),有一个对Mesh的引用 - 但是它尚未声明。因此,例如,您会得到C2143错误。

@ildjarn,这很有趣,因为我从未在任何地方发现过。我可以问一下原因吗? - chris
@ildjarn:你确定吗?我非常确定相反的说法是正确的。我甚至记得标准规定,与一个类建立友元关系等价于与其所有函数建立友元关系...我得找一下引用语。 - David Rodríguez - dribeas
@GarethMcCaughan 我从Mesh.h中删除了#include "MeshList.h",但这只是给我一个错误提示:MeshList不是一个类或命名空间。 - Legion
@David:确实,至少根据VC++ 2010和GCC的说法,它是合法的。抱歉造成干扰。 - ildjarn
2
@Legion:你有一个循环依赖,这通常是代码异味。无论如何,如果你真的觉得这在你的设计中有意义,你需要做的是在MeshList的定义之前前向声明 Mesh(不需要类型的完整定义),然后只在.cpp文件中包含Mesh头文件。 - David Rodríguez - dribeas
显示剩余2条评论

7
循环依赖关系已在其他答案中解释过了...
以下是解决方案:
在 MeshList.h 中: - 用前向声明 class Mesh; 替换 #include "Mesh.h"(因为你只声明了一个指向 Mesh 的指针,所以不需要 include)
在 MeshList.cpp 中: - 添加 #include "Mesh.h"(因为你使用了 Mesh,所以需要这个声明)
你提到的最后一个编译错误是另一个问题。
*mesh->vboHandle_ = 4;

mesh是一个指针。你的代码选择了成员vboHandle_,并试图对其进行解引用(但失败了)。我想你的意思是:

mesh->vboHandle_ = 4; // <-- no leading asterisk

根据David的评论修正了答案。 - Stephan

6
因为在文件Mesh.h中有#include "MeshList.h",所以文件MeshList.h将首先被编译,而类Mesh尚未声明。因此编译器会认为错误行中的Mesh是一个未获得类型的变量名称,从而导致错误。 这是一个制作友元成员函数的示例:
#include <iostream>


class foo;

class bar
{
public:
    void barfunc(foo &f);
};

class foo
{
private:
    friend void bar::barfunc(foo &f);
    int i;
public:
    foo()
    {
        i = 0;
    }
    void printi()
    {
        std::cout << i << '\n';
    }
};

void bar::barfunc(foo &f)
{
    f.i = 5;
}


int main()
{
    foo f;
    bar b;
    b.barfunc(f);
    f.printi();
    return 0;
}

4
问题:您的包含文件存在循环依赖。很不幸,错误信息并不是很理想。
解决方案:如果您将整个类视为朋友而非单个函数,则可以使用类的前向声明来打破循环依赖关系。
// Mesh.h
#ifndef _MESH_H
#define _MESH_H

#include <iostream>

class MeshList;

class Mesh
{
private:
    unsigned int vboHandle_;
    friend class MeshList;
public:
    inline void Out() {std::cout << vboHandle_;}
};
#endif

一些(主观的)指南:

  • 以你无法改变其顺序为基础,将内容包含在反向顺序中。即:首先是STL,第二是第三方头文件,第三是自己的中间件堆栈,第四是当前项目,第五是当前库文件。这样,如果出现冲突,错误就会指向你的头文件。

  • 在类中,将public相关内容放在private内容之前。类的客户端只关心公共接口,没必要让它们在进入接口之前浏览所有不干净的实现细节。


据我所知,如果您的友元声明在第一次使用之前,则不需要前向声明。(我已经在VS中测试了没有'class MeshList;'的类似示例,并且它可以正常工作。) - anxieux
@anxieux:我从来没有完全理解在哪个作用域中注入了friend class后面的名称,如果找不到它;所以我养成了仅仅前向声明类型的习惯。可能会稍微冗长一些,但是嘿:它百分之百地有效! - Matthieu M.

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