Is encapsulation via public constant pointers a good idea?

So here is what I know:

  • It is wise not to expose your ivars directly in your API; rather use accessors
  • A const pointer to an object is not const just means that you can change the object but not redirect to where the pointer points

Here is my situation:

I have several related classes. I want to create a simple class that, through composition, combines them into one logical interface. Each of my private classes already has a public and private difference in its API, so I do not mind exposing them directly to users of my parent class. This means that it would be superfluous for me to write accessors for these Ivars, since classes already control what is open and what is not. But I do not want users to change the actual objects nested in this composite parent class.

So the only way I can do this is to use const pointers for these objects:

 class Parent{ public: EnclosedClass1*const ec1; //users can enjoy the public API of EnclosedClass EnclosedClass2*const ec2; void convenienceMethod(){ //performs inter-related functions on both ec1 and ec2 } } 

Thus, there is no harm in the fact that someone can interact directly with the ec1 and ec2 APIs, but I want to make sure that this is a fairly reliable solution, since at least this person cannot change the actual resource, therefore, const pointers.

Does this make sense, is it good to use const pointers?

Alternatively, I could make them private completely, forget the pointers (this class manages these objects anyway), and just do the extra work of writing accessories for the public functions contained in these objects. But that just seems redundant, right?

+6
source share
6 answers

The traditional way of OOP is to keep the data private and have public accessors if they want to use it outside the class.

If you do not want private data pointers to be changed, you do not provide setters, but only getters.

 class Parent{ private: EnclosedClass1*const ec1; EnclosedClass2*const ec2; public: EnclosedClass1* getEnclosedClass1() const {...}; EnclosedClass2* getEnclosedClass2() const {...}; void convenienceMethod(){ } 

}

Another possibility (e.g. @pmr suggests in the comments below):

 const EnclosedClass1& getEnclosedClass1() const {...}; const EnclosedClass2& getEnclosedClass2() const {...}; 

using the const link you protect the user from checking the value returned by the getters. But you have to make sure that the state of your object is consistent and never has internal pointers to nullptr ; in that case you should throw an exception.

+6
source

There are different things to this approach. The first is that the fact that members have a public and private side to their interface means nothing from the point of view of the encompassing type. A full type can have a different set of invariants that users can violate by independently changing two subobjects.

Even if the Parent type has no invariant (i.e., it does not depend on the state of the subobjects), the proposed approach is not constant. Under the condition const Parent& caller can apply any operation (const, not const) to both subobjects, effectively changing the state of the Parent object. A slightly better approach in this case would be to save the subobjects directly:

 class Parent { public: EnclosedClass1 ec1; EnclosedClass2 ec2; ... 

But I would recommend that you provide accessors to extract data and mutators (if that makes sense) to change the state of objects. Thus, if you need to add invariants later, you can simply insert them there.

+2
source
It makes sense? Yes.

Is it good to use const pointers? Well, not quite. The canonical coding method is to have a getter function for each element and make internal data private. Doing another way will make readers of your code wonder why you are doing this, as this is not standard practice.

So, make your constant pointers private and create getters to access them. For performance reasons, you can create these getter inline d functions, so it won't make any difference in the generated executable, but it will greatly improve the readability of your code.

+1
source

If you have a class that contains two other objects and provides free and full access to these members to your customers, then you should consider whether your class has the right design. The demeter law does not become invalid because you are not using getter, i.e. parent.ec1-> foo is no better than parent.getEc1 (). Foo.

In any case, const members usually have more problems than they are worth it.

0
source

One reason might be that it can be converted to a non-const pointer using constcast.

0
source

Decide what your class should do, and write an interface that provides these things. If it is important that users can tinker with internal data, then create interfaces for this. However, in most cases, the class represents an abstraction, which is different from the sum of things you can do with its internal data, so the interface should not reveal the internal data. For example, a class that contains the names of students usually implements this as some kind of container containing std::string data. This class should not provide a pointer or a reference to a person’s name string. Instead, if the name needs to be fixed, for example, it should be done in a separate editor class; after correction, the saved name can be updated with the new version. There is no need for the class to display the entire std::string interface for users.

0
source

Source: https://habr.com/ru/post/944363/


All Articles