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++ 中是可行的。但是语法会有点不同:
this->
而不是 this.
每个成员 private:
/public:
而不是 private
/public
记得在class
的末尾加上;
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++ 中,引用变量比天主教婚礼更强大,它们在变量的生命周期内没有任何方式重新分配给另一个参考。并且他们必须在出生时分配给一个有效的参考。
但是,如果 a1
和 a2
在对象出生时已知,在对象的生命周期内永远不会改变并保持活动状态,那么以下选项也是可能的:
选项 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?
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++ 中是可行的。但是语法会有点不同:
this->
而不是this.
每个成员 private:
/public:
而不是private
/public
记得在class
的末尾加上;
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++ 中,引用变量比天主教婚礼更强大,它们在变量的生命周期内没有任何方式重新分配给另一个参考。并且他们必须在出生时分配给一个有效的参考。
但是,如果 a1
和 a2
在对象出生时已知,在对象的生命周期内永远不会改变并保持活动状态,那么以下选项也是可能的:
选项 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?