change address of object that keeps raw pointer to its field

86 Views Asked by At

I want to re-allocate a class that has self-reference, named CarJoker.
"Re-allocate" here means = to change the address of an object.

This technique is necessary for making every instance of CarJoker lives in a resizable contiguous array e.g. pool.

I think about using std::move, but it can't move CarJoker::wheels in a way that I wish.
(MCVE)

#include <vector>
#include <iostream>
#include <string>
struct Wheel{
    void setBlade(){}
    void setTwinkle(){}
};
struct CarJoker{
    Wheel wa;
    Wheel wb;
    Wheel wc;
    std::vector<Wheel*> wheels;
    float hp=5;
    CarJoker(){
        wheels.push_back(&wa);
        wheels.push_back(&wb);
        wheels.push_back(&wc);
    }
    void wow(){
        //v want to apply something to every "wheel"
        for(auto wheel:wheels){
            wheel->setBlade();
        }
        //v want to apply something to some certain "wheel"
        wa.setTwinkle();
    }
};

int main(){
    CarJoker car1;
    CarJoker car2=std::move(car1);
    std::cout<<"want to 1 : "<<(car2.wheels[0]== &car2.wa)<<std::endl;
}

With std::move, car2.wheels[0] points to &car1.wa not &car2.wa as I wish.
I know the reason, but that is not my goal, and I don't know an elegant way to fix it.

My poor workaround

Here is an inelegant way (MCVE):-

struct CarJoker{
    Wheel wa;
    Wheel wb;
    Wheel wc;
    std::vector<Wheel*> wheels;
    float hp=5;
    CarJoker(){
        reIni();  //: CHANGE (call a new function)
    }
    void reIni(){ //: CHANGE (create a new function)
        wheels.clear();
        wheels.push_back(&wa);
        wheels.push_back(&wb);
        wheels.push_back(&wc);
    }
    void wow(){
        //v want to apply something to every "wheel"
        for(auto wheel:wheels){
            wheel->setBlade();
        }
        //v want to apply something to some certain "wheel"
        wa.setTwinkle();
    }
};
int main(){
    CarJoker car1;
    CarJoker car2=std::move(car1);
    car2.reIni(); //: CHANGE (call a new function)
    std::cout<<"want to 1 : "<<(car2.wheels[0]== &car2.wa)<<std::endl;
}

Disadvantage:-
1. It is dirty.
2. I have to create a special-name function (reIni()) for every class that has such symptom that live in the pool. My pool has to recognize that function too (e.g. register using template or virtual function).

My poor workaround 2

 struct CarJoker{
    Wheel wa;
    Wheel wb;
    Wheel wc;
    std::vector<Wheel*> getWheels(){  //use this instead of "wheels"
        std::vector<Wheel*> re;
        re.push_back(&wa);
        re.push_back(&wb);
        re.push_back(&wc);
        return re;
    }
    ....
 }

I would work, but I feel that it is crazy to workaround like this.
The restriction increases pitfall for coders.

If wheels happen to need to cache computation-expensive result, now it would be expensive to call getWheels() often.

1

There are 1 best solutions below

5
Remy Lebeau On

You don't need the reIni() method. What you need is to add:

  • a copy constructor and a move constructor which both initialize the wheels member the same way as the default constructor does.

  • a copy assignment operator and a move assignment operator which do not copy/move the wheels member.

Try this:

struct CarJoker{
    Wheel wa;
    Wheel wb;
    Wheel wc;
    std::vector<Wheel*> wheels;
    float hp = 5;

    CarJoker(){
        wheels.push_back(&wa);
        wheels.push_back(&wb);
        wheels.push_back(&wc);
    }

    CarJoker(const CarJoker &src) :
        CarJoker(),
        wa(src.wa),
        wb(src.wb),
        wc(src.wc),
        //wheels(src.wheels),
        hp(src.hp){
    }

    CarJoker(CarJoker &&src) :
        CarJoker(),
        wa(std::move(src.wa)),
        wb(std::move(src.wb)),
        wc(std::move(src.wc)),
        //wheels(std::move(src.wheels)), 
        hp(src.hp){
    }

    // copy assignment and move assignment can be
    // handled with a single implementation that
    // lets the compiler choose between the copy
    // constructor and move constructor as needed...
    CarJoker& operator=(CarJoker rhs){
        wa = std::move(rhs.wa);
        wb = std::move(rhs.wb);
        wc = std::move(rhs.wc);
        wheels = std::move(rhs.wheels);
        hp = rhs.hp;
        return *this;
    }

    ...
}; 

That being said, you really shouldn't be using self-referencing fields to begin with. A single std::array<Wheel, 3> field makes more sense than 3 Wheel fields and a std::vector<Wheel*> field, avoiding this whole problem.

struct CarJoker{
    std::array<Wheel, 3> wheels;
    float hp = 5;

    CarJoker() = default;
    CarJoker(const CarJoker&) = default;
    CarJoker(CarJoker&&) = default;
    CarJoker& operator=(const CarJoker&) = default;
    CarJoker& operator=(CarJoker&&) = default;

    Wheel& wa() { return wheels[0]; }
    const Wheel& wa() const { return wheels[0]; }

    Wheel& wb() { return wheels[1]; }
    const Wheel& wb() const { return wheels[1]; }

    Wheel& wc() { return wheels[2]; }
    const Wheel& wc() const { return wheels[2]; }

    void wow(){
        //v want to apply something to every "wheel"
        for(auto &wheel : wheels){
            wheel.setBlade();
        }

        //v want to apply something to some certain "wheel"
        wa().setTwinkle();
    }
};