2

I know that I cannot mess with the V-Table (in a somewhat sane way) once an object is created. Which means I have to copy an object to change it's type. Does this also hold true with c++11's std::move and friends?

class Base {
  public:
    virtual int type() = 0;

    // more data members I want to avoid to copy
};

class D1 : public Base {
  public:
    int type() {
      return 1;
    }
};

class D2 : public D1 {
  public:
    int type() {
      return 2;
    }
};


int main()
{
  // creating the actual object, type is D1
  D1* obj = new D1();

  // this is what does not work, I want to "change" the object to D2
  D2* obj2 = &std::move<D2>(*obj);

  // cast it to obj2 base class 
  Base* baseObj = static_cast<D1*>(obj2);

  // now I want a "2" here
  int t = baseObj->type();
  printf ("%d\n", t);
}

I do not know the move semantics very well... But is there something I can change a D1 object into D2 (or vice versa) with type safety? (Both classes are virtually the same from the memory layout)

philipp
  • 1,745
  • 1
  • 14
  • 25
  • 1
    You are never going to make a `D2` from a `D1` as `D2` can be considered a superset of `D1`. You either need a `D2` or a child of `D2` to make a `D2`. – NathanOliver May 09 '17 at 16:11
  • Are you sure you want to derive `D2` from `D1`? Why not from `Base`? – BlameTheBits May 09 '17 at 16:26
  • Not without seriously nasty oh-my-god-what-are-you-even-doing `reinterpret_cast`s, anyway – LordAro May 09 '17 at 16:29
  • 3
    The standard answer for a question on `std::move` is "`std::move` doesn't move anything". – alfC May 09 '17 at 16:49
  • You could implement move constructors so that creating a new D2 from an existing D1, or vice versa, happen with minimal overhead. However, the strategy pattern already referenced in an answer is probably much closer to what you want, and has been around forever. – Kenny Ostrom May 09 '17 at 17:02

2 Answers2

4

While you cannot change the type of an existing object, you can easily change the dynamic type of a pointer member and achieve the desired effect. This is known as strategy design pattern.

E.g.:

#include <memory>
#include <iostream>

class Host
{
    struct Strategy
    {
        virtual ~Strategy() = default;
        virtual int type() const = 0;
    };

    struct StrategyA : Strategy { int type() const override { return 1; } };
    struct StrategyB : Strategy { int type() const override { return 2; } };

    std::unique_ptr<Strategy> strategy_;

public:
    Host()
        : strategy_(new StrategyA)
    {}

    int type() const { return strategy_->type(); }

    void change_strategy(int type) {
        switch(type) {
        case 1: strategy_.reset(new StrategyA); break;
        case 2: strategy_.reset(new StrategyB); break;
        default: std::abort();
        }
    }
};


int main() {
    Host host;
    std::cout << host.type() << '\n';
    host.change_strategy(2);
    std::cout << host.type() << '\n';
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
2

It seems to me you are not familiar with what std::move actually does.

As others have said, std::move doesn't actually move anything. It obtains an xvalue (a variable that has a name, but can have its resources reused or transfered to another object) reference from an lvalue (essentially a named variable), so, std::move is nothing but a cast. It doesn't create any new object. Read more about it here and here.

Still about move semantics topic, std::move is mostly useful so that you can force rvalue-aware methods to receive and reuse resources in variables that you absolutely know that can have their resources moved. To get a more indepth insight of this, I'd recommend reading What are move semantics?. For instance, one of its uses is creating an object from a temporary (e.g, objects created inside a function and then returned):

#include <vector>
#include <iostream>

class A {
  public:
    // A very large resource
    std::vector<int> resource;

    // Constructs our very large resource
    A(): resource(1024, 0) { std::cout << "Default construct" << std::endl; }

    // Move (reuses) a very large resource from an instance
    A(A && other) : resource(std::move(other.resource)) { 
      std::cout << "Move construct" << std::endl;
    }
};

Now, A's move constructor is only called when the other object is an rvalue (or an xvalue), such as:

A foo(A a) { return a; }
int main() {
  A a = foo(A());
  return 0
}

In this scenario, before foo() gets called, a temporary A() is created and passed in as argument. foo() returns it, but since it is a temporary , it fits as an rvalue and is passed to the move constructor of A when constructing A a = foo(A()).

Inside the move constructor of A(), std::move(other.resource) is used when constructing another resource to call the move constructor of std::vector so that it can try to use whatever it can from other.resource instead of creating everything from scratch again (and then copying).

But as stated previously, std::move on itself doesn't move anything, but it is there to convey intent to move, help the compiler do the right thing (and other programmers to read it and understand faster what you meant).

So, to answer your question directly, no, there isn't anything that'd let you transform an object into another, other than constructing a new object. If you are sure that you are going to destroy obj (which you are not doing, by the way), you can implement an constructor of D2 that accepts an rvalue of D1:

class D2 {
  public:
    D2(D1 && d1) : resource(std::move(d1.resource)) { d1.resource = nullptr; }
}

int main() { 
  D1 * obj = new D1();
  D2 * obj2 = new D2(std::move(*obj));
  delete obj;
}

There are other things to consider when doing this, though, such as destructors of moved objects and other details. I'd recommend reading more about the subject and also maybe using a different method of achieving what you are doing, such as the Strategy pattern mentioned in another answer.

Community
  • 1
  • 1
Leonardo
  • 1,834
  • 1
  • 17
  • 23