How to initialize a const reference member to another member (std vector) in C++ initializer list

793 Views Asked by At

I did the following as a cheap way to allow read-only access to a member container _numbers via numbers:

class Foo {
    Foo() : _numbers({}), numbers(_numbers) {
    // some code that populates `numbers` via `numbers.push_back(...)`
}

private:
    std::vector<int> _numbers;
public:
    const std::vector<int>& numbers;
}

However, doing so I see that numbers is empty, while in other cases it will contain the same elements as _numbers.

To be more precise, it seems to be undefined behavior. In my real example (of which this a simplified version) I have multiple reference-container pairs with this scheme, where the populated data is visible in the const-reference member for some pairs, and for some it is not.

Any idea whats wrong with this? Any help is deeply appreciated.

EDIT Here is a minimal working example:

#include <vector>

struct Foo2 {
public:
     const int max1;
     const int center1;

    Foo2(const int max1_);
private:
    std::vector<int> _numbers1, _numbers2;

public:
    const std::vector<int>& numbers1, numbers2;
};

Foo2::Foo2(const int max1_)
    : max1(max1_), center1(max1_/2),
      _numbers1({}), _numbers2({}),
      numbers1(_numbers1),
      numbers2(_numbers2)
{
    cout << max1 << endl;

    for (int i=0; i<center1; i++) {
        _numbers1.push_back(i);
        cout << "A: " << i << endl;
    }
    for (int i=center1; i<max1; i++) {
        _numbers2.push_back(i);
        cout << "B: " << i << endl;
    }

    for (int i: numbers1) {
        cout << "A: " << i << endl;
    }
    for (int i: numbers2) {
        cout << "B: " << i << endl;
    }
}

which gives the following Output when initializing Foo2 f(8):

8
A: 0
A: 1
A: 2
A: 3
B: 4
B: 5
B: 6
B: 7
A: 0
A: 1
A: 2
A: 3

i.e. numbers2 does not see the contents of _numbers2 while for numbers1 it seems to work.

2

There are 2 best solutions below

6
HolyBlackCat On BEST ANSWER

const vector<int>& numbers1, numbers2; — Here, only the first variable is a reference. You need & before the second variable to make it a reference as well. Then the code should work.

But I have to say that what you're doing is a really bad idea. You're paying for a convenient syntax with memory overhead, non-assignability, and possibly speed overhead.

Use getters instead: const vector<int>& numbers1() const {return _numbers1;}. Yes, you will have to type the extra () every time.

0
Serge Ballesta On

Hmm... The main cause of the problem was given by @HolyBlackCat: numbers2 was not a reference but an independant copy.

But IMHO there is a more fundamental problem. With:

public:
    const std::vector<int>& numbers;

You promise the compiler, that once initialized, the vector referenced by numbers will no longer change. Which is a lie, because the underlying vector changes...

Lying to the compiler is a sure path to UB: it will work sometimes, and will suddenly break because the compiler is free to change its actual code so that no changes to _member will be reflected in members.

Possible fixes:

  • use std::vector<int>& numbers = _numbers; (no const). This one will be fine, but you loose the ability to present a read only reference

  • get a fully initialized reference when you need it through a getter (i.e. a method):

      const std::vector& numbers() {
          return _numbers;
     }
    

    Again it is fine, provided _number no longer changes after complete initialization of Foo

  • use a dedicated object, implicitely convertible to a vector that will be fully initialized before being used:

      struct Bar {
          std::vector<int>_numbers;
    
          Bar(bool first) : Bar(0, first) {};
          Bar(int max, bool first) {
              cout << max << endl;
              int center = max / 2;
    
              if (first) {
                  for (int i = 0; i < center; i++) {
                      _numbers.push_back(i);
                      cout << "A: " << i << endl;
                  }
              }
              else {
                  for (int i = center; i < max; i++) {
                      _numbers.push_back(i);
                      cout << "B: " << i << endl;
                  }
              }
          }
    
          operator const std::vector<int>& () {
              return _numbers;
          }
      };
      struct Foo2 {
      public:
          const int max1;
          const int center1;
    
          Foo2();
          Foo2(const int max1_);
      private:
          Bar _numbers1, _numbers2;
    
      public:
          const std::vector<int>& numbers1, &numbers2;
      };