Is it possible for a C++ class to include an instance of its own type the same way we can in Java? For example, something like this:
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;
}
}
Now I want the same thing or a workaround in C++.
Yes, it's doable in C++. But the syntax would be a little different:
this->
instead of this.
private:
/public:
instead of private
/public
per member
remember to have ;
at the end of the class
A*
as member (or std::uniqe_ptr<A>
or std::shared_ptr<A>
or std::weak_ptr<A>
).
Items 1-3 are merely syntax. Item 4 is an essential difference between Java and C++:
In Java an object variable is a reference to the object while in C++ an object variable is a value. This is why you can't hold in C++ a direct member of yourself, as is, the size of the object would be infinite (A holding an actual value of A, holding an actual value of A, ... recursively).
In Java when A holds an A, it just holds a reference to the other A (yes, you can still access recursively the referenced A, but it is not part of your size, you just hold a reference to it, it is stored elsewhere in memory. The addition to your size is just the size of a reference).
You can achieve similar semantics in C++ with reference variables or pointers, by adding &
for a reference or *
for a pointer:
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 Garbage Collector reclaims unused memory while in C++ the programmer needs to handle that, possibly with C++ tools such as smart pointers.
Java Garbage Collector reclaims unused memory via Trace by Reachability, C++ smart pointers are based on scope lifetime. Additionally, C++ shared_ptr
is based on reference counting which has its advantages, but is subject to reference cycles possible leak of memory, which should be avoided with proper design of your code.
The C++ version of "holding myself" may look like any of the below (or variations of them), depending on the exact need:
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;
}
};
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);
}
};
* need to make sure you avoid cyclic ownership leak.
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;
}
};
* the option of std::weak_ptr
is relevant in case of possible cyclic dependency, a1 and a2 are owned elsewhere and might not be alive.
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;
}
};
Option 4 code example: http://coliru.stacked-crooked.com/a/92d6004280fdc147
Note that using A&
(reference to A) as a member, is not an option, as in C++ reference variables are stronger than Catholic wedding, they're for the lifetime of the variable without any way to reassign to another reference. And they must be assigned to a valid reference when born.
However, if a1
and a2
are known when the object is born, never change and stay alive for the duration of the object's lifetime, then the following option is also possible:
* this option is mainly to show that it is possible to hold references, however in most cases a pointer option (like option 1), or a const pointer member, would be more suitable.
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);
}
This option is based on the fact that passing an object to its own constructor is legal.
class A {
A& a1;
A& a2;
public:
A(A& a1, A& a2): a1(a1), a2(a2) {}
A& getA1(){
return a1;
}
A& getA2(){
return a2;
}
};
int main() {
A a1(a1, a1); // legal, see note above
A a2(a1, a1);
}
Code for option 5a: http://coliru.stacked-crooked.com/a/0d73dcc0d1783b7d
The last and final option, below, is mainly to present the possibility of going forward with option 5 (or 5a) and allowing the change of the reference held by A.
This option is possible since C++20. However, it is to be noted that using a pointer for this purpose would most probably be a better choice.
*since C++20, note that this option is mainly to show the possibility, pointers would probably be a better choice here.
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);
}
};
Code for option 5b: http://coliru.stacked-crooked.com/a/43adef3bff619e99
See also: Why can I assign a new value to a reference, and how can I make a reference refer to something else?