2

I needed to use some class today that followed this basic design:

class Task {
public:
    Task() {
        Handler::instance().add(this);
    }
    virtual void doSomething() = 0;
};

class Handler {
    std::vector<Task*> vec;
    //yea yea, we are locking the option to use default constructor etc
public:
    static Handler& instance() {
        static Handler handler;
        return handler;
    }

    void add(Task* task) {
        vec.push_back(task);
    }

    void handle() {
        for (auto t : vec) {
            t->doSomething();
        }
    }
};

template <class T, int SIZE>
class MyTask : public Task {
     T data[SIZE];
public:
    virtual void doSomething() {
        // actually do something
    }
};
//somewhere in the code:
Handler::instance().handle();

now, my class is something like

class A {
    MyTask<bool, 128> myTask;
public:
    A(int i) {}
};

the way I wanted to do it is having a map where instances of A are values

 static std::map<int, A> map = {
     {42, A(1948)},
     {88, A(-17)}
 };

first to clarify something - this code needs to run on a real time embedded system so I'm not allowed to allocate memory using new for several legacy reasons.

My problem was that the actual objects in the map weren't the ones I explicitly created and so they didn't register in the Handler class (so I didn't get the benefit of the Handler::handle calls).

I tried figuring a nice way to solve this without doing something ugly like first creating an array of A then only point to these objects in the map for example.

I never used move semantics before but I've read little bit about them and thought they can be my solution.

however, after reading this answer (specificaly the very first example) it seemed that I can't really benefit anything from using move semantics.

I tried it anyway (coz why the heck not...) and did something like this instead:

 static std::map<int, A> map = {
     {42, std::move(A(1948))},
     {88, std::move(A(-17))}
 };

now for my surprise the copy constructor of MyTask was still called (I put print in it to verify) but for some reason now the handler registration worked well and my instances enjoyed the doSomething() calls.

I tried reading more thoroughly about std::move to understand what exactly happened there but couldn't find the answer.

can anyone explain it? does std::move moves the this pointer somehow? or maybe it just caused the registration to happen correctly somehow and had nothing real to do with the moving attempt

thanks

edit:

to further clarify what I'm asking:

I understand the use of std::move was not contributing to what's being done there.

But for some reason it did get my objects in the map to get the doSomething() calls through the handler. I'm looking for that reason

on a side note as it probably belongs to a different question - is there any decent way to initialize a map this way without the overhead of creating each object twice?

Community
  • 1
  • 1
user2717954
  • 1,822
  • 2
  • 17
  • 28
  • 1
    This question is still not really clear to me. Your example doesn't really make sense -- where does this `std::map` come into play and how is it in any way related to `Handler`? Please create a real [mcve] – AndyG Dec 01 '16 at 20:10
  • why does it matter where the map comes into play? and the Handler registers the Task objects created – user2717954 Dec 01 '16 at 20:56
  • Aside: if you aren't allowed to do memory allocations, then you probably aren't allowed to use use `std::map` (unless you give it a custom allocator that doesn't violate your constraints). –  Dec 01 '16 at 21:03

1 Answers1

1

Your question has a lot more in it than it needs to but I think I understand the root question here. The std::map constructor receives an initialization_list, you're calling (5) from this list. Objects are copied out of an initializer_list when iterating over it rather than moved because a copy of an initializer_list doesn't copy the underlying objects. The same affects other std containers, here is an example with vector to demonstrate. (live link)

#include <vector>
#include <iostream>

struct Printer {
    Printer() { std::cout << "default ctor\n"; }
    Printer(const Printer&) { std::cout << "copy\n"; }
    Printer(Printer&&) { std::cout << "move\n"; }
};

int main() {
    std::vector<Printer> v = {Printer{}};
}

if you use {std::move(Printer{})} you'll add another move into the mix that the compiler can't easily get optimize away.

Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
  • I sort of understand this, the real question is why did it actually solve my problem? – user2717954 Dec 01 '16 at 20:53
  • If your problem is that you cant use `new` then it didn't. map uses new – Ryan Haining Dec 01 '16 at 21:00
  • I can use a map (yea I know it sounds stupid but these are the rules in my company) – user2717954 Dec 01 '16 at 21:02
  • @user2717954: Or you don't understand *why* the rules are what they are. –  Dec 01 '16 at 21:04
  • or I understand but also know it may sound stupid to some people and since that's not the subject of the question I don't think I should get into it – user2717954 Dec 01 '16 at 21:13
  • It'd be helpful if you could elaborate a bit then, are you not allowed to use *raw* new? Could you use `std::make_shared` for example? – Ryan Haining Dec 01 '16 at 21:23
  • I guess I could - still not totally aware to all the c++11 and later features. assuming so trivially solves this (just like using the oh forbidden new). I'm more interested in the reason for why the things I did actually changed anything – user2717954 Dec 01 '16 at 22:06
  • @user2717954 I don't suppose this style guide or requirements is published publicly? – Ryan Haining Dec 01 '16 at 22:14