C++ class 可以包含它自己类型的成员吗

Can a C++ class include a member of its own type

C++ class 是否可以像我们在 Java 中那样包含自己类型的实例?例如,像这样:

public class A {
  private A a1;
  private A a2;
  
  A getA1(){
   return a1;
  }

  A getA2(){
   return a2;
  }

  void setA1(A a1){
   this.a1 = a1;
  }

  void setA2(A a2){
   this.a2 = a2;
  }
}

现在我想要在 C++ 中做同样的事情或解决方法。

是的,它在 C++ 中是可行的。但是语法会有点不同:

  1. this-> 而不是 this.

  2. 每个成员
  3. private:/public: 而不是 private/public

  4. 记得在class

    的末尾加上;
  5. A* 作为成员(或 std::uniqe_ptr<A>std::shared_ptr<A>std::weak_ptr<A>)。


项目 1-3 仅仅是语法。第4条是Java和C++的本质区别:

  • 在 Java 中,对象变量是 对象的引用 而在 C++ 中,对象变量是 。这就是为什么你不能在 C++ 中持有你自己的直接成员,因为 对象的大小将是无限的 (A 持有 A 的实际值,递归地持有 A 的实际值,...)。

    在Java中,当A持有一个A时,它只是持有一个对另一个A的引用(是的,你仍然可以递归访问引用了A,但它不是你的尺寸的一部分,你只是持有它的一个引用,它存储在内存的其他地方。除了你的尺寸只是一个引用的尺寸)。

    您可以在 C++ 中使用 引用变量 指针 实现类似的语义,方法是添加 & 作为引用或 * 对于指针:

    A& a2 = a1; // a2 is a reference to A, assigned with a reference to a1
                // note that a1 above is assumed to be also of type A&
    
    A* a2 = a1; // a2 is a pointer to A, assigned with the address stored in a1
                // note that a1 above is assumed to be also of type A*
    
  • Java 垃圾收集器回收未使用的内存,而在 C++ 中,程序员需要处理它,可能使用 C++ 工具,例如 智能指针 .

  • Java 垃圾收集器通过Trace by Reachability, C++ smart pointers are based on scope lifetime. Additionally, C++ shared_ptr is based on reference counting回收未使用的内存,这有其优点,但受制于引用循环可能的内存泄漏,应通过适当的设计来避免你的代码。


"holding myself" 的 C++ 版本可能类似于以下任何一种(或它们的变体),具体取决于具体需要:

选项 1 - A 持有不拥有 a1 和 a2

class A {
   A* a1 = nullptr;
   A* a2 = nullptr;

public: 
   A* getA1(){
      return a1;
   }

   A* getA2(){
     return a2;
   }

   void setA1(A* a1){
     this->a1 = a1;
   }

   void setA2(A* a2){
     this->a2 = a2;
   }
};

选项 2 - A 拥有 a1 和 a2 作为 独特资源

class A {
   std::unique_ptr<A> a1 = nullptr;
   std::unique_ptr<A> a2 = nullptr;

public: 
   A* getA1(){
      return a1.get();
   }

   A* getA2(){
     return a2.get();
   }

   void setA1(std::unique_ptr<A> a1){
     this->a1 = std::move(a1);
   }

   void setA2(std::unique_ptr<A> a2){
     this->a2 = std::move(a2);
   }
};

选项 3 - A 持有 a1 和 a2 作为 共享资源*

* 需要确保避免循环所有权泄漏。

class A {
   std::shared_ptr<A> a1 = nullptr;
   std::shared_ptr<A> a2 = nullptr;

public: 
   auto getA1(){
      return a1;
   }

   auto getA2(){
     return a2;
   }

   void setA1(std::shared_ptr<A> a1){
     this->a1 = a1;
   }

   void setA2(std::shared_ptr<A> a2){
     this->a2 = a2;
   }
};

选项 4 - A 持有指向 a1 和 a2 的弱指针*

* std::weak_ptr 的选项与可能的循环依赖相关,a1 和 a2 在别处拥有并且可能不存在。

class A {
   std::weak_ptr<A> a1 = nullptr;
   std::weak_ptr<A> a2 = nullptr;

public: 
   std::shared_ptr<A> getA1(){
      return a1.lock();
   }

   std::shared_ptr<A> getA2(){
     return a2.lock();
   }

   void setA1(std::shared_ptr<A> a1){
     this->a1 = a1;
   }

   void setA2(std::shared_ptr<A> a2){
     this->a2 = a2;
   }
};

选项 4 代码示例:http://coliru.stacked-crooked.com/a/92d6004280fdc147


请注意,使用 A&(对 A 的引用)作为成员不是一种选择,因为在 C++ 中,引用变量比天主教婚礼更强大,它们在变量的生命周期内没有任何方式重新分配给另一个参考。并且他们必须在出生时分配给一个有效的参考。

但是,如果 a1a2 在对象出生时已知,在对象的生命周期内永远不会改变并保持活动状态,那么以下选项也是可能的:

选项 5 - A 持有对 a1 和 a2 的引用*

* 这个选项主要是为了说明可以持有引用,但是在大多数情况下是一个指针选项(如选项1),或者一个const指针成员,会更合适。

class A {
   A& a1;
   A& a2;

public:
   A(A& a1, A& a2): a1(a1), a2(a2) {}

   // using ref to self as a placeholder
   // to allow the creation of "the first A"  
   A(): a1(*this), a2(*this) {}
  
   A& getA1(){
      return a1;
   }

   A& getA2(){
      return a2;
   }
};

int main() {
   A a1;
   A a2(a1, a1);
}

下面的最后一个也是最后一个选项主要是为了展示继续进行选项 5 并允许更改 A 持有的引用的可能性。

此选项自 C++20 起可用。但是,需要注意的是,为此目的使用指针很可能是更好的选择。

选项 5b - A 持有对 a1 和 a2 的引用,并允许设置!*

*从C++20开始,注意这个选项主要是为了展示可能性,指针在这里可能是更好的选择。

class A {
   // all same as in option 5
public:
   void set(A& a1, A& a2){
      A other(a1, a2);
      // placement new that changes internal ref
      // is valid since C++20
      new (this) A(other);
   }
};

选项 5b 的代码:http://coliru.stacked-crooked.com/a/43adef3bff619e99

另请参阅:Why can I assign a new value to a reference, and how can I make a reference refer to something else?