使用 pimpl 习语时 c++ 中的构造函数和析构函数

Constructor and destructor in c++ when using the pimpl idiom

我来自 Java,它有不同的方式来处理关于 class 实现的私有和必须隐藏的内容,它还有一个垃圾收集器,这意味着不需要析构函数。

我学习了如何在 C++ 中实现 class 的基础知识,但我需要更好地理解如何在使用 pimpl 惯用语时实现 class,尤其是构造函数和析构函数。

hpp 文件:

class MyClass{
public:
  MyClass();
  MyClass(std::vector<int>& arr);
  ~MyClass();

 
private:
  struct Impl;
  Impl* pimpl;
};

cpp 文件:

#include "MyClass.hpp"
using namespace std;


struct MyClass::Impl{
  vector<int> arr;
  int var;
};

我写了一个 class 的示例代码,我需要继续使用 pimpl idiom(这意味着我不能改变 pimpl idiom 的使用方式),我正在寻找答案对于这些问题:

MyClass::MyClass(){}
MyClass::MyClass(vector<int>& arr,int var){}
MyClass::~MyClass(){}

编辑:

我会怎么做:

MyClass::MyClass(): pimpl(new Impl) {}

MyClass::MyClass(vector<int>& arr,int var): pimpl(new Impl) {
  pimpl->arr=arr;
  pimpl->var=var;
}

MyClass::~MyClass(){
  delete pimpl;
}

这是在现代 C++ 中实现 PIMPL 习语的正确方法示例:

foo.hpp

#pragma once

#include <memory>

class Foo {
public:
  Foo();
  ~Foo();

  Foo(Foo const &) = delete;
  Foo &operator=(Foo const &) = delete;

  Foo(Foo &&) noexcept;
  Foo &operator=(Foo &&) noexcept;

  void bar();

private:
  class impl;
  std::unique_ptr<impl> pimpl_;
};

foo.cpp:

#include "foo.hpp"

#include <iostream>

class Foo::impl {
public:
  impl() = default;
  ~impl() = default;

  impl(impl const &) = default;
  impl &operator=(impl const &) = default;

  impl(impl &&) noexcept = default;
  impl &operator=(impl &&) noexcept = default;

  void bar() { std::cout << "bar" << std::endl; }
};

Foo::Foo() : pimpl_(new impl{}) {}
Foo::~Foo() = default;

Foo::Foo(Foo &&) noexcept = default;
Foo &Foo::operator=(Foo &&) noexcept = default;

void Foo::bar() { pimpl_->bar(); }

main.cpp:

#include "foo.hpp"

int main(int argc, char const *argv[]) {
  Foo foo;
  foo.bar();
  return 0;
}

有几句话要说:

  • 使用智能指针(唯一或共享)保存对 'impl' 对象的引用。它会像 JAVA 一样帮助您控制对基础对象的引用数量。在此示例中,由于 Foo class 消失了 'impl' 自动销毁,因此无需手动观察 'impl' 对象
  • 的生命周期
  • 'impl' 必须始终在 *.cpp 文件(或一堆文件)或内部 *.hpp and/or *.cpp 文件中完全声明和定义,以便您前面的用户 class(即在本例中 'Foo')聚合 'impl' 不可能看到你的 'impl' class 的任何变化和内容,这就是 PIMPL 习语存在的原因: 为用户保留前面 class 的界面,所有更改大部分应在隐藏 'impl' class
  • 中完成
  • 前面class的析构函数必须始终在*.cpp文件中定义,因为编译器必须知道如何销毁从*.hpp文件中看不到的'impl'前面class被声明
  • 具有 rvalue-references 的特殊函数(move-ctor 和 move-assignment 运算符)也必须在 *.cpp 文件中定义,原因与前一节相同