2

Accordingly to C++ best practices on IsoCpp we shouldn't have a const or reference data member: C.12: Don’t make data members const or references

But it does not specify if a pointer to a const object is allowed or not. Consider the example:

class Connection {
    gsl::not_null<const Network*> m_network;
    ...

public:
    [[nodiscard]] const Network* getNetwork() const;
    ...
}

// Implementation
const Network* Connection::getNetwork() const {
    return m_network;
}

Is it still conformant with the best practice described? The pointer isn't const but the data the pointer points to is.

Should we remove const from member declaration but still mark the getter return type as a pointer to const?

  • 2
    Perfectly OK. The pointer itself isn't const. It can safely point to a const. If a member is const the assignment constructor is disabled and you would have to write your own. That doesn't happen in your case. – doug May 06 '22 at 01:28
  • 1
    There are valid reasons to make a member const. Uncommon ones but they do exist – Joe May 06 '22 at 01:41
  • There are also good usages for reference members. And there are many classes, which should not be assignable. Lots of reasons to exactly do the opposite from what is stated. In the end, this specific guideline is so weak, I would not accept it for a coding guideline. – Sebastian May 06 '22 at 03:16

2 Answers2

2

Yes, you can

If you have a const data member, the copy and move constructor are deleted. But this only applies if the pointer itself is const, not the pointed value.

const & or const * are only fine when the class is uncopyable (like a "manager class" of some sort)

Arthur M
  • 69
  • 4
2

It depends.

The reason this recommendation exists, is to make your type copyable and assignable, with both having matching semantics.

So consider this:

struct Connection {
  Network const& network;
  /**/
};

Copying this is perfectly fine, right? But then assigning breaks because network will not be reseatable.

So in this case, replacing that member with an e.g. std::shared_ptr<Network const> is fine. Presumably several connections can use the same network.

But -- if you do not want sharing but each object to have its own copy of the member, then you are left with reconstructing that member on assignment, because the old object cannot be changed. Depending on the object that might not be what you want to do.

For example:

struct Connection {
  std::shared_ptr<Network const> network;
  std::unique_ptr<std::vector<Entry> const> properties;
};

Aside from a pointer-to-vector being bad form (double indirection for no good reason). Reconstructing a vector anew requires a new memory allocation, while just reusing the old will not (assuming there was enough memory allocated).

Granted, this is a bit of constructed example, and there would maybe be a way to go around that. But the point is, that the similarty of your copy and assignment will heavily depend on the quality of the copy and assignment of the member object.

In such a case the same reasoning that applied to the situation without the pointer similarly applies to the situation with the pointer. You will have to decide whether that is acceptable for your type.


However, I hold that not every type must be assignable. You can make the conscious decision to have a type that just cannot be assigned to. Then and only then, just have a const member without any indirection.

bitmask
  • 32,434
  • 14
  • 99
  • 159
  • Thanks for the enlightening answer. You're right about Connection and Network relationship, in my case the Network is const in a given Connection. I don't want the Connection to be able to change anything on the Network, so that's why I didn't used a std::shared_ptr to keep ownership only with Network. With this information, the use of `const Network*` still applies, right? About the references comment, I was using that, but that gave me object lifetime issues, so I changed the Networks to the heap. Thank you again! – Vinícius Ferrão May 06 '22 at 16:14
  • For lifetime issues there is also the option of `std::optional` – Sebastian May 06 '22 at 17:55
  • 1
    I'm not sure if `std::optional` would make sense, since a given Connection does not exist without a Network, so it's not optional. The lifetime issue that I had is that the Network object is created inside other object, and after this object is destroyed the reference was lost. Now it just `std::move` the Network to where it should be and we don't lost it. – Vinícius Ferrão May 06 '22 at 18:23
  • Would in this case `std::unique_ptr` be the better choice than `const Network*`? – Sebastian May 06 '22 at 19:26
  • Yes, I agree, the scenario you describes sounds like either unique or shared pointer. Depending on whether or not you intend to keep more that exactly one Connection for a given Network object. As I stated in the answer, I see no issue with a shared pointer holding a const object. At any rate, I would advise against lugging around raw pointers. – bitmask May 06 '22 at 20:18