2

Consider the following:

#include <vector>

class Base 
{
public:
    Base() : x(0) {}
    int x;
};

class Derived : public Base 
{
public:
    Derived(double z0) : Base(), z(z0) {}
    double z;
};

class Foo 
{
public:
    // How to specify a default value for parameter vec0,
    // consisting of two Base objects?
    Foo(std::vector<Base> vec0 = ? ? ? ) : vec(vec0) {}
    std::vector<Base> vec;
};

class Bar 
{
public:
    // foo1.vec needs to be initialized with two Base objects.
    // foo2.vec needs to be initialized with two Derived objects.
    Bar() : foo1(? ? ? ), foo2(? ? ? ) {}
    Foo foo1;
    Foo foo2;
};

int main() 
{
    Bar bar;
    // Code here will want to use Base class pointers to access the elements
    // in bar.foo1.vec and bar.foo2.vec.
}
  1. How to specify a default parameter in the Foo constructor?

  2. In the Bar constructor initializer list, how to specify a vector of Base objects for foo1, and a vector of Derived objects for foo2?

  3. How to title this question so others needing to solve this problem can find it?

JeJo
  • 30,635
  • 6
  • 49
  • 88
sifferman
  • 2,955
  • 2
  • 27
  • 37

2 Answers2

2

How to specify a default parameter in the Foo constructor?

Just use initializer list syntax, either explicitly declaring containing elements:

class Foo {
public:
    Foo(std::vector<Base> vec0 = {Base(), Base()}) : vec(vec0) {}
    std::vector<Base> vec;
};

or implicitly declaring with yet another initializer list:

class Foo {
public:
    Foo(std::vector<Base> vec0 = {{}, {}}) : vec(vec0) {}
    std::vector<Base> vec;
};

But if your intention is to create by default list with size 2, I think it's better to do so this way:

class Foo {
public:
    Foo() : vec(2) {}
    std::vector<Base> vec;
};

In the Bar constructor initializer list, how to specify a vector of Base objects for foo1, and a vector of Derived objects for foo2?

You can't do this. You need to either use templates or vector of pointers.

Template example:

template<class V>
class Foo {
public:
    Foo(std::vector<V> vec0 = {{}, {}}) : vec(vec0) {}
    std::vector<V> vec;
};

class Bar {
public:
    // No need to explicitly initialize foo1
    Bar() : foo2({{1.},{2.}}) {}
    Foo<Base> foo1;
    Foo<Derived> foo2;
};
sklott
  • 2,634
  • 6
  • 17
  • 1
    However, using templates V could be any class. If you want that V extends Base, you need to use std::enable_if like in this [link](https://stackoverflow.com/questions/30687305/c-equivalent-of-using-t-extends-class-for-a-java-parameter-return-type). – Doch88 Aug 07 '19 at 08:29
  • 2
    @Doch88 or just `static_assert(std::is_base_of_v)` somewhere in `Foo`, or just use `Base`s members with `V` objects – Caleth Aug 07 '19 at 14:08
  • Thank you for your great answer. – sifferman Aug 09 '19 at 16:11
2

The @sklott 's answer explains well, how the initialization can be done using the initializer list. I would like to focus on the solution in-which Foo has a vector of pointers to Base. Using std::vector<std::shared_ptr<Base>>, you could rewrite @sklott 's solution as follows:

#include <memory>   // std::shared_ptr
#include <utility>  //  std::move

class Foo 
{
public:
    Foo(std::vector<std::shared_ptr<Base>> vec0 = { 
              { std::make_shared<Base>(), std::make_shared<Base>() } 
           }
        )
        : vec{ std::move(vec0) } 
    {}
    std::vector<std::shared_ptr<Base>> vec;
};

class Bar 
{
public:
    Bar() 
        : foo1{}  // calls Foo's default cont'r: initialized with two Base objects.
        , foo2{   // initialized with two Derived objects.
            { std::make_shared<Derived>(1.0), std::make_shared<Derived>(1.0) } 
          }
    {}
    Foo foo1;
    Foo foo2;
};

The above code would have an undefied behaviour, if we do not provide a virtual destructor to the Base class. Therefore, to make it complete solution:

class Base 
{
public:
    Base() : x(0) {}
    int x;
    virtual ~Base() = default;   // don't forget to provide a virtual destructor
};
JeJo
  • 30,635
  • 6
  • 49
  • 88
  • Is there a way to initialize `Foo.vec` using a shortened syntax indicating the size of the vector such as @sklott used with `Foo() : vec(2) {}` (instead of needing to explicitly show each vector element in the initializer list as in `{ std::make_shared(), std::make_shared() }`)? It would be useful to provide a parameter indicating the size of the vector. (I know this wasn't part of the original requirements.) – sifferman Aug 07 '19 at 23:55
  • @sifferman Of course, you can:https://wandbox.org/permlink/r9c931MkBiUwy8RB. But I always prefer `std::make_shared`, for the reason explained here:https://stackoverflow.com/questions/20895648/difference-in-make-shared-and-normal-shared-ptr-in-c. – JeJo Aug 08 '19 at 08:01
  • 1
    Although @sklott properly answered the question, and was first to answer, your answer helped see me through to (I think) a better solution to my problem. Thanks. I learned some things! – sifferman Aug 09 '19 at 16:18
  • It appears the construct `BasePtrVec(2, std::make_shared())` is problematic. Because it is a vector of pointers, and is constructed by creating a first `shared_ptr`, which is then copied to the remaining vector elements, we end up with a vector of shared pointers that all point to the same object. – sifferman Aug 13 '19 at 03:24
  • @sifferman Yes, that exactly I mentioned in the code(above link) as a comment: *// "vec0 is empty? construct once a `std::make_shared()` and copy to make `2` elements out of it"*. For different objects, we need to explicitly construct two different ones, like in the answer. – JeJo Aug 13 '19 at 06:02