在C++类头文件中,我需要定义所有私有函数和变量吗?

3
在C++类中,我的私有函数和变量应该放在类头文件定义的私有部分还是类源文件中?为什么?
例如:
// Header
class MyClass {
public:
    void doSomething();
private:
    int a = 0;
}

// Source
void MyClass::doSomething()
{
    // Do something with `a`
}

或者

// Header
class MyClass {
public:
    void doSomething();
}

// Source
int a = 0;

void MyClass::doSomething()
{
    // Do something with `a`
}

我一直认为,在编程时,应该使函数/变量的作用域尽可能小。那么,将变量 a 的作用域限制在源文件的范围内,是不是最好的选择呢?


5
第二个例子中的变量 a 的作用域比第一个例子中的“小”得多,它根本不是成员变量,而是在全局范围内声明的。 - Mat
7
第二个版本中没有成员a,两个版本不等同。 - StoryTeller - Unslander Monica
@Vivick 我明白了,我问这个问题是因为我的一些类头文件可能会填充私有变量和方法,这些变量和方法只在一个源文件中使用,这会让事情变得非常混乱。 - Ari Seyhun
@Acidic,遗憾的是int MyClass::a = 0只适用于静态成员,你必须在头文件中坚持使用默认值。 - Vivick
也许你正在寻找pimpl - user2672107
显示剩余2条评论
2个回答

2
它们不是等价的。第一个例子。
// Header
class MyClass {
public:
    void doSomething();
private:
    int a = 0;
}

// Source
void MyClass::doSomething()
{
    ++a;
    cout << a << endl;
}

int main()
{
    MyClass x, y;
    x.doSomething();
    y.doSomething()
}

输出

1
1

Second example

// Header
class MyClass {
public:
    void doSomething();
}

int a = 0;

// Source
void MyClass::doSomething()
{
    ++a;
    cout << a << endl;
}

int main()
{
    MyClass x, y;
    x.doSomething();
    y.doSomething()
}

输出

1
2

在第一个例子中,a 是一个类变量,因此 xy 都有自己的 a 副本。在第二个例子中,只有一个全局变量 a,因此输出结果不同。


这很有道理,但是像结构体或函数这样的一些基本东西呢? - Ari Seyhun
@Acidic 是的,你可以在类的定义中前向声明成员类型和成员函数,然后稍后再为它们提供定义。 - Caleth
如果一个结构体或函数只在一个文件中使用,那么在该文件中定义和声明它们而不是使用头文件是有意义的。只在一个文件中使用的函数应该被声明为静态的,这样它们就不会在文件外可见。 - john
@john 在文件中被声明为静态变量,但不是类的一部分?只需在源文件中使用static void myFunction() { ... }即可。 - Ari Seyhun
@Acidic 这正是我所想的。但是,如果一个函数需要成为类的一部分,那么这就不是一个选项。 - john

0

您可以使用pimpl惯用法... 或者,您可以使用pimpl惯用法的这个变体,其中实现的内存由接口类直接提供:

在文件MyClass.hpp中:

class MyClass{
  private:
    std::byte buffer[N];
  public:
    MyClass();
    void public_method();
    ~MyClass();
  };

在类 MyClass.cpp 中:
#include "MyClass.hpp"
namespace{
  struct MyClassImpl{
     private:
       int val=0;
     public:
       void secret_method(){/*...*/}
     };
  inline const MyClassImpl& 
  get(const std::byte* buffer){
    //In theory, in C++17 you should use std::launder here to be standard compliant
    //in practice, compilers generate the expected code without std::launder
    //and with std::launder they generate horrible and inefficient code!
    return *reinterpret_cast<const MyClassImpl*>(buffer);
    }
  inline MyClassImpl& 
  get(const std::byte* buffer){
    //idem here to be c++17 standard compliant launder is necessary
    //in practice, it would be a mistake.
    return *reinterpret_cast<MyClassImpl*>(buffer);
    }
 }

 MyClass::MyClass(){
   new(buffer) MyClassImpl{};
   }
 MyClass::~MyClass(){
   get(buffer).~MyClassImpl();
   }
 void
 MyClass::public_method(){
    /*....*/
    get(buffer).secret_method();
    /*....*/
    }

与传统的pimpl惯用法相比:

  • 优点:内存访问更少,不需要在堆上分配内存,更高效

  • 缺点:容易出错,在接口中实现的大小会“泄漏”。


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