1

I have a class with a private field. This field is written only inside the class, but must be readable from outside. Which option is preferable and why? First, with const reference

class Test {
public:
    const int &field; // can read outside
    
    inline Test() noexcept: field(_field) {}
    
    void someMethod() {
        _field = 123; // writing occurs only inside the class
    }
private:
    int _field;
};

Or second, with inline getter:

class Test {
public:
    void someMethod() {
        _field = 123; // writing occurs only inside the class
    }
    
    inline int field() const noexcept { // can call outside
        return _field;
    }  
private:
    int _field;
};
  • 1
    IMO this is exactly what getters are meant for. Const-ref-to-private is less flexible and generally not a common pattern, so it may confuse readers. – 0x5453 Oct 14 '20 at 15:30
  • 1
    `inline` are unneeded for class method definition into the class. – Jarod42 Oct 14 '20 at 15:39
  • I would shy away from using leading underscores. See https://youtu.be/ieERUEhs910?t=325 – Den-Jason Oct 14 '20 at 15:44
  • @Den-Jason why? Your link literally says that this is valid. – Aykhan Hagverdili Oct 14 '20 at 17:09
  • Your leading underscored code *may* start its life in a valid context, but then when it is later refactored and moved around, renamed etc, it *could* end up being in an invalid context. This is why constructs that are "OK in some contexts" should generally be avoided, since the context can change. Macros often use underscored names, so when the macro is expanded there can be a name clash. Plus it's also a bit Python-looking :D – Den-Jason Oct 14 '20 at 17:11
  • @Den-Jason a lot can go wrong if you're refactoring the code however you wish. This is a very common naming scheme for private variables in C++ indeed. Macros can't use "single underscore+lowercase". – Aykhan Hagverdili Oct 14 '20 at 17:14
  • 1
    Only in the QT world as far as I've seen. And they're just wrong :P – Den-Jason Oct 14 '20 at 17:14
  • @AyxanHaqverdili see the discussion here: https://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier – Den-Jason Oct 14 '20 at 17:18
  • @Den-Jason I've seen that question. The accepted answer just says which identifiers are reserved – Aykhan Hagverdili Oct 14 '20 at 17:24

1 Answers1

2

There are a whole bunch of reasons to avoid the reference-typed data member:

  • It bloats the object size
  • All assignment operators default to deleted
  • The default copy-constructor becomes wrong, tying the lifetime of the new copy to that of the copied-from object.
  • The object is no longer standard-layout nor trivially copyable

If you really really want to expose a reference, do so as the return type of a member function:

const int& field() const { return field_; } // avoid leading underscores in application code

Generally though, a value return will be higher-performance and easier for the calling code to use.

  • Copying a small value is as cheap as forming a reference (which copies a pointer)
  • The compiler doesn't have to worry about aliasing
  • The programmer doesn't have to worry about aliasing
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • "avoid leading underscores in application code" Why? It's a common naming scheme for private variables – Aykhan Hagverdili Oct 14 '20 at 17:04
  • 1
    @AyxanHaqverdili: No... it's a common naming device in automatically-generated code, which has to use names that don't conflict with either implementation-reserved identifiers or identifiers that the application programmer wants to use. The fact that people then hold up the auto-generated code as an example to pattern their own code after, without understanding the tradeoffs that code-generation wizards face, makes them **cargo cult** programmers. Don't be part of a cargo cult. – Ben Voigt Oct 14 '20 at 18:11
  • 1
    I myself would not use a trailing underscore for this purpose either, but the question didn't give enough context to choose a good name for the member variable. – Ben Voigt Oct 14 '20 at 18:16
  • I've seen that convention used a lot, and I can't personally see anything wrong with it. – Aykhan Hagverdili Oct 14 '20 at 18:19
  • 1
    @AyxanHaqverdili: There are two things wrong. One, you are mindlessly imitating other code without understanding ("cargo cult"). Two, you have broken the assumption made by the code generation tools that they can safely use identifiers with leading underscores without conflicting with variable names chosen by the programmer. So they will have to move to a new pattern that doesn't overlap with real usage, but then the cargo cult programmers will pick up on that pattern and imitate it, reintroducing conflicts, and it will be a never-ending cycle of more and more insane variable names. – Ben Voigt Oct 14 '20 at 18:23
  • Bring on the GUID-Prefix :P – Den-Jason Oct 14 '20 at 18:24
  • 1
    @Den-Jason: LOL! But don't expect the cargo cult programmers to run the "New GUID" tool before following the pattern... we will be lucky if they even realize that it is a GUID. – Ben Voigt Oct 14 '20 at 18:25
  • I've worked with a lot of programmers over my years and the only ones I've seen using the "private leading underscore idiom" are those who learned C++ with QT and those who transitioned over from Python. Or both. – Den-Jason Oct 14 '20 at 18:29
  • @BenVoigt while I see your point, I don't agree with it. Just following a convention shouldn't mean "following a cult" as it simply makes privates stand out the same way `m_` and others do. Some people just hate underscores but oh well. C++ has a diverse community – Aykhan Hagverdili Oct 14 '20 at 18:31
  • Let's also throw lambdas into the mix - more underscores! :P https://blog.feabhas.com/2014/03/demystifying-c-lambdas/ – Den-Jason Oct 15 '20 at 22:55
  • @Den-Jason: Lambdas have nothing to do with underscores (although the boost lambda library may have used them for parameter-reference placeholders). The compiler has no need to actually generate an identifier like `_CompilerGeneratedName_`, it can just make the connection in its intermediate representation. And if it does use a name, it could be one that would not even be a valid C++ identifier if it appeared in the source code. Compiler intermediate stages are not constrained in the same way that automatic source-code generators are. – Ben Voigt Oct 16 '20 at 15:22