0

I'm having trouble with getting 2 different objects of the same type to have pointers to different objects using a parameter pack. Here is Controller.h:

using expand_type = int[];

template<typename... Ts> class Controller {
public:
    Controller(int i, string s, Ts... args): id(i), name(s) {
        expand_type{ (add_component(&args), 0)... };

    }
    void add_component(Test2* a) {
        device_.push_back(a);
    }
    void print() {
        cout<<name<<":"<<endl;
        cout<<"================="<<endl;
        for (vector<Test2*>::size_type i = 0; i < device_.size(); ++i) {
            cout<<device_[i]->print()<<endl;
        }
        cout<<"================="<<endl;
    }
private:
    vector<Test2*> device_;
    int id;
    string name;
};

and here is my Test2.h:

class Test2 {
public:
    Test2(): x(0) {}
    Test2(int a): x(a) {}
    int print() const {
        return x;
    }
private:
    int x;
};

My problem is when I make two seperate Controller objects, they share their Test2 objects in their respective vectors. Here is my main:

int main() {
    Test2 b(69);
    Test2 c(666);
    Test2 d(943754);
    Controller<Test2, Test2, Test2, Test2> x(2, string("Peter"), 70, b, c, d);
    Controller<> y(2, string("Pietje"));
    Controller<Test2, Test2, Test2, Test2> z(3, string("Jan"), 909, 808, 1, 2);

    x.print();
    y.print();
    z.print();

    cout<<"hello"<<endl;
    return 0;
}

then the output is:

Peter:
=================
0
808
1
2
=================
Pietje:
=================
=================
Jan:
=================
0
808
1
2
=================
hello

I want the different objects of Controller to have different pointers to different Test2 objects, and I'v3 no clue how.

Also, as a side problem; my first pointer always becomes 0, unless it's the only object in the vector.

Any help is appreciated!

Ryan
  • 1
  • 2
  • 1
    The first problem is that you are adding pointers to local variables, which go out of scope after the constructor. Hence, accessing these is undefined behavior. Why do you have a variadic template in the first place if all you can process are `Test2` objects? Try to explain what you are actually trying to do. – Nico Schertler Dec 24 '19 at 17:41
  • How else would you make sure you can pass any number of arguments to the object constructor? I'm new to this so I really don't know. About the local variables, aren't I putting these pointers into the vector, which stores them? – Ryan Dec 24 '19 at 17:48
  • I am trying to make it so you can make a Controller object with any number of Test2 objects, which are stored into the Test2* vector. – Ryan Dec 24 '19 at 17:49
  • Passing an initializer list or a vector (possible as an rvalue reference) seem to be the better thing to do. See [this question](https://stackoverflow.com/questions/1579719/variable-number-of-parameters-in-function-in-c). – Nico Schertler Dec 24 '19 at 18:12

1 Answers1

0

Side-stepping the funky behavior, your main problem here is ownership: Does Controller own the devices, and if not who? Let's say the controller doesn't own the devices. Then stuff like this will cause problems

Controller<...> z(3, string("Jan"), 909, 808, 1, 2);
                                    ^^^ temporary object's lifetime ends immediately
                                        it won't have a valid address to point to

Your experience will be much better once you plan it out. Let's say Controller owns the devices: just use a vector of Test2 and you're set.

Next issue is the variadic template. It looks like the device_s are all the same type, and the only use for the variadic template is to have a variable number of constructor arguments. If so, just use a variadic template for the constructor, and make the class a plain ol' simple type:

class Controller {
public:
    template<typename... Ts>
    Controller(int i, string s, Ts&&... args): id(i), name(s), device_ { std::forward<Ts>(args)... } { }
    // ...
};

Put it all together, and everything works as expected:

#include <vector>
#include <string>
#include <iostream>
using namespace std;

class Test2 {
public:
    Test2(): x(0) {}
    Test2(int a): x(a) {}
    int print() const {
        return x;
    }
private:
    int x;
};

class Controller {
public:
    template<typename... Ts>
    Controller(int i, string s, Ts&&... args): id(i), name(s), device_ { std::forward<Ts>(args)... } { }

    void print() {
        cout<<name<<":"<<endl;
        cout<<"================="<<endl;
        for (auto& device : device_) {
            cout<<device.print()<<endl;
        }
        cout<<"================="<<endl;
    }
private:
    vector<Test2> device_;
    int id;
    string name;
};

int main() {
    Test2 b(69);
    Test2 c(666);
    Test2 d(943754);
    Controller x(2, string("Peter"), 70, b, c, d);
    Controller y(2, string("Pietje"));
    Controller z(3, string("Jan"), 909, 808, 1, 2);

    x.print();
    y.print();
    z.print();

    cout<<"hello"<<endl;
    return 0;
}
Peter:
=================
70
69
666
943754
=================
Pietje:
=================
=================
Jan:
=================
909
808
1
2
=================
hello

Demo: https://godbolt.org/z/bLjgaf


UPDATE: Per your request, let's say Controller only stores raw pointers and doesn't own the Test2 devices. First, you have to pass references to the contructor so the object's address is accessible:

class Controller {
public:
    template<typename... Ts>
    Controller(int i, string s, Ts&... args): id(i), name(s), device_ { &args... } { }
    // ...
};

Then you have to make sure every object you pass it outlives the controller:

Test2 b(69);
Test2 c(666);
Test2 d(943754);
Controller x(2, string("Peter"), b, c, d); // this is fine
Controller y(2, string("Pietje"));         // this is fine
Controller z(3, string("Jan"), 909, 808, 1, 2); // uh oh!

So far, we've only ever talked about stack-allocated Test2 objects. You can also dynamically allocate an object and get a pointer to it. This pointer only has a single owner, and you can transfer ownership with std::move:

std::unique_ptr<Test2> = std::make_unique<Test2>(42);

You can also create a pointer with shared ownership. You can copy this pointer, and the last surviving pointer will clean up the object:

std::shared_ptr<Test2> = std::make_shared<Test2>(42);

BUT, I can't recommend this if you're trying to learn about memory models. A lot of times people use it as a way of avoiding thinking about memory models. If everything has a clear owner, it makes your life much easier.

parktomatomi
  • 3,851
  • 1
  • 14
  • 18
  • Thanks for the clarification, but the point of the vector is to have Test2 pointers in them, so how would you do it then? The devices are owned by no-one, or that's the idea anyway. Also, what does `Ts&&` and `std::forward(args)...` mean? – Ryan Dec 24 '19 at 18:00
  • Why is the point to have a vector of pointers? I mean, you can, but the pointers have to point to an object. And if those objects come from elsewhere, they have to outlive `Controller`, or else the pointers will become invalid. So, you can have a bunch of Test2's outside the struct and pass those in, you just have to have a mental model of what those objects' lifetimes are. I'll add an example to my answer. – parktomatomi Dec 24 '19 at 18:05
  • `Ts&&` and `std::forward` is a pattern called perfect forwarding. It avoids unnecessary copying if all you're doing is passing the arguments of a function or constructor to another function or constructor instead of using them: https://eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c . – parktomatomi Dec 24 '19 at 18:07
  • Yeah, I do have a model. The question is how do you get different pointers to different objects of the same class into different vectors of different objects of the same class. – Ryan Dec 24 '19 at 18:15
  • Or should i just make it so Controller owns the Test2's? It would be easier, but it isn't really what I had planned. – Ryan Dec 24 '19 at 18:16
  • What do you mean by "different vectors of different objects of the same class". Are there multiple controllers and a single pool of devices? – parktomatomi Dec 24 '19 at 18:19
  • It's pretty normal to just have a context object that owns all your controllers and devices. That way all the objects stay alive (and their pointers stay valid) until the context is destroyed. Then you can store raw pointers for references without worrying – parktomatomi Dec 24 '19 at 18:31
  • Each Controller object has a vector, which needs to store pointers to different Test2 objects. There are let's say 5 Controllers and 50 Test2's. Each Controller would then have 10 Test2's for example. The current problem is that each Controller object would save the last 10 pointers to Test2 objects, which you can see in my output in the original question. – Ryan Dec 24 '19 at 18:39