C++ Copy/Move constructor in policy based design

244 views Asked by At

While exploring Policy Based Design pattern in C++, I stumbled upon one problem which I could not find a solution to: How do you write copy and move constructors for policy based classes in a generic way without referring to member variables inside the policy class?

Here's an example:

class Foobase {
public:
  Foobase(int v) :val(v) { }
protected:
  int val;
};

template <typename Base>
class Foo : public Base {
public:
  Foo(int v):Base(v) { }
  // how can I initialize Base() class without referring to it's protected members?
  Foo(const Foo<Base>& other) :Base(other.val) { }
};

int main() {
  Foo<Foobase> foo(5);
  auto foo2 = foo;
}

In the above code, copy constructor of class Foo uses protected member variable to initialize Base class. Is there any other way to initialize Base except above? What are the best practices in such cases?

Updated question:

Answer by @LogicStuff clarifies copy constructor part of the question, but doesn't answer the move constructor question. See the updated example code in which class Foo can also have member variables.

class Foobase {
public:
  Foobase(int v) :val(v) { }
  Foobase(const Foobase&) = default;
  Foobase(Foobase&&) noexcept = default;
  Foobase& operator= (const Foobase&) = default;
  Foobase& operator= (Foobase&&) noexcept = default;
  void Print() const {
    std::cout << val << std::endl;
  }
protected:
  int val;
};

template <typename Base>
class Foo : public Base {
public:
  // works fine
  Foo(std::string str, int v):Base(v), name(str) { }

  // works fine
  Foo(const Foo<Base>& other) :Base(other), name(other.name) { }

  // I'm doubtful about this c'tor, if I move `other` for initializing Base,
  // how can I initialize `name` variable?
  Foo(Foo<Base>&& other)
    :Base(std::move(other)), name(std::move(other.name) /* will this be valid?? */) { }

  // can we have copy and assignment operator for this class?

  void Print() {
    std::cout << "name = " << name << std::endl;
    Base::Print();
  }

private:
  std::string name;
};

int main() {
  Foo<Foobase> foo("foo", 5);
  auto foo2 = std::move(foo);
  foo2.Print();
}
1

There are 1 answers

5
LogicStuff On

You can simply use Base's copy constructor:

Foo(const Foo<Base>& other) : Base(other) { }

or not define it at all and leave it up to the compiler, if you're not going to do something extra there.

Actually, it can look like this and your example will still compile:

template <typename Base>
class Foo : public Base {
public:
  using Base::Base;
};

After your edit:

Now you have to supplement a constructor taking two parameters (as you did):

template <typename Base>
class Foo : public Base {
public:
  Foo(std::string str, int v) : Base(v), name(str) { }

  void Print() {
    std::cout << "name = " << name << std::endl;
    Base::Print();
  }

private:
  std::string name;
};

but you've also defined the special member functions which would be identically generated by the compiler otherwise (that means they're correct). This is also the case with your original example. Both versions of Foo are move-constructible.