7

I'm coming to C++ from C# and const-correctness is still new to me. In C# I could declare a property like this:

class Type 
{
    public readonly int x;
    public Type(int y) 
    {
        x = y;
    }
}

This would ensure that x was only set during initialization. I would like to do something similar in C++. The best I can come up with though is:

class Type
{
private:
    int _x;
public:
    Type(int y) { _x = y; }
    int get_x() { return _x; }
};

Is there a better way to do this? Even better: Can I do this with a struct? The type I have in mind is really just a collection of data, with no logic, so a struct would be better if I could guarantee that its values are set only during initialization.

Justin R.
  • 23,435
  • 23
  • 108
  • 157
  • 5
    `struct` and `class` are both classes in C++. The differences are default accessibility (struct: default is public) and inheritance (struct: default is public). – dyp Sep 12 '13 at 22:45

4 Answers4

10

There is a const modifier:

class Type
{
private:
   const int _x;
   int j;

public:
    Type(int y):_x(y) { j = 5; }
    int get_x() { return _x; }
    // disable changing the object through assignment
    Type& operator=(const Type&) = delete;
};

Note that you need to initialize constant in the constructor initialization list. Other variables you can also initialize in the constructor body.

About your second question, yes, you can do something like this:

   struct Type
   {
      const int x; 
      const int y;

      Type(int vx, int vy): x(vx), y(vy){}
      // disable changing the object through assignment
      Type& operator=(const Type&) = delete;
   };
Nemanja Boric
  • 21,627
  • 6
  • 67
  • 91
  • 1
    However, you won't be able to assign to an object of type `Type` any more (default assignment ops are deleted). – dyp Sep 12 '13 at 22:45
  • Yes, you will able only to copy objects during creation (using copy-constructor). – Nemanja Boric Sep 12 '13 at 22:50
  • @DyP Realistically, you *could* still modify stuff with `const_cast` or by pointer trickery (eg, `memcpy(&myType, &newX, sizeof(int))`), but that kind of defeats the whole purpose of "read only" so I would definitely recommend against such nonesense. – Nicu Stiurca Sep 12 '13 at 23:03
  • This seems like the right approach, but when I compile this struct I get a warning, C4512 'Type': assignment operator could not be generated. – Justin R. Sep 12 '13 at 23:25
  • @fatcat1111 Oh, it's a warning indicating that operator= will not be able to generate. You need to remove it explicitly. I will edit my answer. – Nemanja Boric Sep 12 '13 at 23:26
  • @fatcat1111 can you try now? – Nemanja Boric Sep 12 '13 at 23:31
  • 2
    `Type& operator=(const Type&) = delete;` would be better . The private operator hack was only required before language support was added for deleting a function; and would cause undefined behaviour if you tried to call the `operator=` from an accessible context – M.M Jun 05 '19 at 04:08
3

Rather than a collection of constants, you could have a constant collection. The property of being constant seems to pertain to your use case, not the data model itself. Like so:

struct extent { int width; int height; };

const extent e { 20, 30 };

It's possible to have specifically constant data members of a class, but then you need to write a constructor to initialize it:

struct Foo
{
    const int x;
    int & y;
    int z;

    Foo(int a, int & b) : x(a + b), y(b), z(b - a) {  }
};

(The example also shows another type of data member that needs to be initialized: references.)

Of course, structs and classes are the same thing.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • It sounds like in this case, making the type constant is up to the user of the type - e.g. when they get an instance, they need to declare that as const. Is that correct? – Justin R. Sep 12 '13 at 22:57
  • @fatcat1111: Right. In the example, `extent` just models some concept which has no requirement for any part of it to be constant, but your *use case* wants the data to be constant. So you declare a constant of that type and initialize it. – Kerrek SB Sep 12 '13 at 22:58
1

You can initialize class const members with constructor. If you need add some other logic in constructor, but in .cpp file not in .h, you can create a private method and call it in constructor.

File.h

    class Example 
    {
    private:
        const int constantMember1;
        const int constantMember2;
        const int constantMember3;
        void Init();
    public:
        Example(int a, int b) :constantMember1(a), constantMember2(b), constantMember3(a + b) {
           //Initialization
           Init();
        };
    };

File.cpp

void Init()
{
//Some Logic intialization
}
Joma
  • 3,520
  • 1
  • 29
  • 32
0

This is not exactly answering the question asked, but if you wanted to have the simplicity of directly accessing member variables in a struct without getters, but wanted to ensure that nobody could modify the values, you could do something like this:

#include <iostream>

using namespace std;

class TypeFriend;

struct Type
{
  const int &x;
  const int y;

  Type (int vx, int vy):x (_x), y (vy), _x (vx)
  {
  }

private:
  friend class TypeFriend;
  int _x;

};

struct TypeFriend
{
  TypeFriend (Type & t):_t (t)
  {
  }

  void setX (int newX)
  {
    _t._x = newX;
  }

private:
  Type & _t;
};

int main ()
{
  Type t (1, 2);
  TypeFriend tf (t);

  cout << t.x << "," << t.y << endl;

  // t.x = 6;  // error: assignment of read-only location ‘t.Type::x’
  // cout<<t.x << ","<<t.y<<endl;

  tf.setX (5);
  cout << t.x << "," << t.y << endl;

  return 0;
}

The result of running this is:

1,2
5,2

Type::x cannot be modified externally, so it is read-only, but via TypeFriend it can be changed. This can be useful if you wanted to expose a simple interface of direct member access for reading, but wanted to restrict how those members could be changed.