Home > Mobile >  C : Deep Copy Diamond Pointer Structure
C : Deep Copy Diamond Pointer Structure

Time:07-13

In my simulation software, I generate objects with pybind11. So all objects are stored in std::shared_ptr with a not known structure at compile time. For parallelisation of my simulation I need to run the same configuration with different seeds. I want to implement the duplication of these objects in one call on the C side.

Following a minimal example, where I want a2 to be a deepcopy of a, with the diamond structure.

// Type your code here, or load an example.
#include <memory>
#include <map>
#include <iostream>

class C{};
class B{
    public:
    B(std::shared_ptr<C> c):c(c){}
    std::shared_ptr<C> c;
};
class A{
    public:
    A(std::shared_ptr<B> b1, std::shared_ptr<B> b2):b1(b1), b2(b2){}
    std::shared_ptr<B> b1;
    std::shared_ptr<B> b2;
};

auto init(){
    auto c = std::make_shared<C>();
    auto b1 = std::make_shared<B>(c);
    auto b2 = std::make_shared<B>(c);
    auto a = std::make_shared<A>(b1,b2);
    return a;
}


int main(){
    auto a = init();
    auto a2 = a; //deepcopy of a, where b1 and b2 of the copy point to the same object C
}

The only solution I came up with is passing a map<pointer,shared_ptr>. This allows for lookup if the shared_ptr has already been deep copied. (Here I have some problems with the typing as I need to dynamicly cast back the types. This feels really ugly and bugprone.)

CodePudding user response:

You can use std::shared_ptr<void> to type-erase all your shared pointers, using std::static_pointer_cast to go to and from your actual types.

using Seen = std::set<std::shared_ptr<void>>;

template <typename T>
std::shared_ptr<T> deep_copy(std::shared_ptr<T> source, Seen & seen) {
    if (auto it = seen.find(std::static_pointer_cast<void>(source)); it != seen.end()) {
        return std::static_pointer_cast<T>(*it);
    }
    auto dest = make(*source, seen);
    seen.insert(std::static_pointer_cast<void>(dest));
    return dest;
}

You can then either write constructors that take an existing instance and a seen map to deep copy the members, allowing them to be private.

template <typename T>
std::shared_ptr<T> make(const T & source, Seen & seen) {
    return std::make_shared<T>(source, seen);
}    

class C{
    public:
    C(){}
    C(const C &, Seen &){}    
};    
class B{
    std::shared_ptr<C> c;
    public:
    B(std::shared_ptr<C> c):c(c){}
    B(const B & other, Seen & seen):c(deep_copy(other.c, seen)){}
};
class A{
    std::shared_ptr<B> b1;
    std::shared_ptr<B> b2;
    public:
    A(std::shared_ptr<B> b1, std::shared_ptr<B> b2):b1(b1), b2(b2){}
    A(const A & other, Seen & seen):b1(deep_copy(other.b1, seen)), b2(deep_copy(other.b2, seen)){}
};

int main(){
    auto a = init();
    Seen a2_seen;
    auto a2 = deep_copy(a, a2_seen);
}

Or you can have overloads of make for each type, where make<T> would need to be friended by T if the members were private.

std::shared_ptr<C> make(const C &, Seen &) {
    return std::make_shared<C>();
}

std::shared_ptr<B> make(const B & other, Seen & seen) {
    auto c = deep_copy(other.c, seen);
    return std::make_shared<B>(c);
}

std::shared_ptr<A> make(const A & other, Seen & seen) {
    auto b1 = deep_copy(other.b1, seen);
    auto b2 = deep_copy(other.b2, seen);
    return std::make_shared<A>(b1, b2);
}
  •  Tags:  
  • c
  • Related