1

I have a class, say C, where one of the member data, say X, depends on user input. The user input may differ on every run, and in my current design, all instances of my classes stores a reference to the same object X.

How can I tweak the design so that it allows default constructor with no arguments?

This way I can use copy assignment/copy constructor, make arrays of C, use temporary rvalue etc.

Below is a minimal working example illustrating my question. In the case I work with, Tag refers to some external resources.

#include <iostream>
#include <string>
#include <vector>
#include <cassert>

using namespace std;

struct Tag {
    int N;
    string tag;
};

template<typename T>
struct Vec {
    const Tag& tag;
    T* vec;

    Vec(const Tag& tag_) : tag(tag_) {
        vec = new T[tag.N];
    }

    ~Vec() {
        delete [] vec;
    }
};

Tag make_tag(vector<string>& args) {
    assert(args.size() == 3);
    int N = stoi(args[1]);
    return Tag {N, args[2]};
}

vector<string> arguments(int argc, char* argv[]) {
    vector<string> res;
    for(int i = 0; i < argc; i++)
        res.push_back(argv[i]);
    return res; 
}

int main(int argc, char* argv[]) {
    vector<string> args = arguments(argc, argv);
    Tag tag0 = make_tag(args);
    Tag tag1;
    Vec<double> vec(tag0);
    return 0;
}
einpoklum
  • 118,144
  • 57
  • 340
  • 684
user103500
  • 77
  • 1
  • 8
  • 2
    You could use a pointer instead of a reference. It is hard to answer this for sure without seeing an example of your actual code, and the problem you are trying to overcome. Please provide a [mcve] – Remy Lebeau Oct 26 '18 at 21:22

1 Answers1

1

How can I tweak the design so that it allows default constructor with no arguments?

Well, three options that I would suggest:

  • The fancy way: Use a std::optional<std::reference_wrapper<T>> member. std::reference_wrapper is for putting reference in place where you're not sure a reference would just work as-is. std::optional<T> holds either a T or a nullopt (i.e. no-value). This has the benefit of the default initializer for the optional being argument-less, so you can possibly use a default constructor for the argument-less case of C.

  • The old-school way: Use a plain pointer member instead of the reference. Initialize it to nullptr on no-argument construction. (@RemyLebeau also suggested this in a comment.)

  • The smart-ass RAII way: Replace your class C with an std::optional<C>. This means that argument-less construction doesn't actually construct a C - it just keeps a nullopt, delaying the actual construction for later. This solution is relevant when you must maintain the invariant of C holding resources throughout its existence; it also has the benefit of keeping the reference member of C as const.

I purposely am not considering your MWE (you said it is just illustrative), and am offering a more general answer.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • I think the fancy way is the best solution. The MWE do illustrate an issue I am facing: this reference object T controls how class C is acquiring resources from free store (heap). If I call default constructor with T as a nullopt, then RAII is violated. Is there any way around it? – user103500 Oct 27 '18 at 00:06
  • 1
    Indeed, if you use either fancy way or the old-school way, you are making C **a non-RAII class**. There's no way around it: Either you get trivial construction (and no resource acquisition), or you can maintain the invariant of holding resources during object lifetime, but then the construction can't be trivial. – einpoklum Oct 27 '18 at 16:18
  • Thanks the RAII way is the probably too much for my code base though. std::optional is only in C++17. – user103500 Oct 30 '18 at 22:55
  • 1
    @user103500: It's available as `std::experimental::optional` in C++14, and you can use `boost::optional` instead, if you like. A self-contained `optional` implementation can also be found [here](https://github.com/TartanLlama/optional). – einpoklum Oct 30 '18 at 23:20