2015-02-20 1 views
1

Итак, у меня есть то, что я считаю довольно распространенной проблемой C++ с участием объекта . Проблемный домен таков: анимированный gif может иметь много кадров , а рисование кадра зависит от контекста других фреймов Gif.Копирование конструкторов для классов с взаимной ссылкой/циклической зависимостью

Так у меня есть эта модель (очень упрощенная на этот вопрос, но должен иллюстрировать):

class Gif { 
    std::vector<Frame> _frames; 

    // Makes Frame objects from file and puts them in _frames 
    Gif(const char* file){...}; 

    // For clarity, works and copies the _frames member 
    Gif(const Gif& other) = default; 
}; 

class Frame { 
    Frame(...){ 
    // Make a frame object 
    } 
    void draw(const Gif& context){ 
    // draws self considering context's other frames 
    } 
}; 

Это работает, но для простоты и избегать рисований Frame в неправильного контекста, я хочу метод draw не принимает аргумент context. Так я думал о создании Frames с опорным элементом _contextconst :

class Frame { 
    const Gif& _context; 

    // Make a frame object 
    Frame(const Gif& context) _context(context){...} 

    // Explicit default copy-constructor, for clarity. Breaks horribly 
    // when called from Gif's copy constructor, since the new Frame will 
    // reference the wrong context, which might be deleted. 
    Frame(const& Frame other) = default; 


    void draw(){ 
    // draws self considering _context's other frames 
    } 
}; 

компилируется, но перерывы ужасно при копировании Gif с. Объекты Frame новых Gif являются копиями в порядке, но они ссылаются на неверный контекст , который уже был удален.

я считать, что константные члены ссылки, вероятно, плохая идея для объектов, которые вы собираетесь копировать ... Должен ли я использовать указатель и явно сбросить его для новых Frame с в теле пользовательских Gif " s конструктор копирования?

Если да, то какой вид указателя (raw/smart)? Разве нет хорошего метода C++ 11 для создания это происходит «автоматически»?

Или я должен нарушать циклическую зависимость и как?

EDIT (спасибо KillianDS): Чтобы быть ясным, вы начинаете с gif1 с некоторых кадров (frame1_1, frame1_2, ...), которые указывают на gif1, и теперь вы хотите скопировать gif1 в gif2 с скопированными кадрами (frame2_1, frame2_2, ...), но это указывает на gif2?

+1

Чтобы быть ясным, вы начинаете с 'gif1' с некоторыми кадрами (' frame1_1, frame1_2, ... '), которые указывают на' gif1' и теперь вы хотите, чтобы скопировать '' gif1' в gif2' с скопированные кадры ('frame2_1, frame2_2, ...'), но это указывает на 'gif2'? – KillianDS

+0

yes @KillianDS, точно –

+0

Кажется, пользователи класса Gif могут получить доступ к объекту (ссылкам) класса 'Frame'. Почему бы не заставить 'Gif' вместо этого публиковать какой-то тип указателя' FramePointer', который содержит 'Gif const * context' и' Frame * frame'? – dyp

ответ

0

Так что я подумал об этом немного подробнее и пришел к этому рабочему примеру. Это на самом деле вторая версия, первая из которых используется unique_ptr и push_back. Эта версия использует emplace_back и совершенную пересылку за предложение @ KillianDS. Чтобы запретить создание новых кадров без прохождения через Gif::add_frame, также используется FrameFriend, как предложено here.

class Gif; 
class FrameFriend { 
    friend class Gif; 
private: 
    FrameFriend(){} 
}; 

class Frame { 
public: 
    int _number; 
    const Gif& _context; 

    // Constructors are public but a FrameFriend is needed and only Gif 
    // can make one. And frames can't be copied or copy-assigned. 
    Frame(int number, const Gif& gif, const FrameFriend&) : _number(number),_context(gif) {} 
    Frame(Frame&& other) = default; 
    Frame operator=(Frame other) = delete; 
    Frame(const Frame& another) = delete; 

    // ... but they can be constructed from ther frames 
    Frame(const Frame& another, const Gif& gif, const FrameFriend&) 
    : _number(another._number), 
     _context(gif) {} 

    void draw(){} 
}; 


class Gif { 
    // Each gif owns its frames exclusively 
    std::vector<Frame> _frames; 

    // Privately, frames can be copied from other gifs 
    // calling the appropriate private constructor of Frame 
    void copy_frames(const Gif& other){ 
    _frames.clear(); 
    _frames.reserve(other._frames.size()); 
    for (auto& f: other._frames) { 
     _frames.emplace_back(f, *this, FrameFriend()); 
    } 
    } 

public: 
    // Public constructors 
    Gif(){}; 
    // Public constructors. Copying and assigning both call 
    // copy_frames() which updates the the gif context 
    Gif(const Gif& other) { copy_frames(other); } 
    Gif& operator=(Gif& other) { copy_frames(other); return *this;} 
    // Move constructor can be the default 
    Gif(Gif&& other) = default; 

    // Add a frame 
    void add_frame(){ 
    _frames.emplace_back(_frames.size(), *this, FrameFriend()); 
    } 

    // The set of frames can be publicly accessed and frames can even be modified 
    // individually, but as no 
    std::vector<Frame>& frames() { return _frames; }; 
}; 
+0

Вам больше не нужны никакие указатели, просто работайте с регулярными переменными и, возможно, используйте ['emplace_back'] (http://en.cppreference.com/w/cpp/container/vector/emplace_back), чтобы ускорить работу , Я также использовал ['reserve'] (http://en.cppreference.com/w/cpp/container/vector/reserve) в коде копирования, чтобы избежать ненужных распределений. – KillianDS

+0

@KillianDS: что вы имеете в виду, мне не нужны указатели? 'emplace_back' и' reserve' звучат как хорошие идеи. –

+0

@ KillianDS Я понял. Благодаря! Процесс получился именно тем, как я его первоначально запросил, а 'emplace_back' и отличная пересылка выглядят так же, как и идиома C++ 11, которую я искал. Я могу принять ваш ответ, но, возможно, вы можете отредактировать, чтобы упомянуть, что эта идиома явно лучше. –

1

В принципе, вы хотите, чтобы скопированные рамки указывали на скопированный gif. Это не будет работать с конструктором копии по умолчанию на обоих уровнях, поскольку вы не слепо копируете объекты. Ваш Gif конструктор копирования, вероятно, должен стать чем-то вроде этого:

class Gif { 
    std::vector<Frame> _frames; 

    Gif(const Gif& other) : some_var(other.some_var), _frames(other.frames) ... 
    { 
     for(auto& frame: _frames) 
     { 
      frame.update_context(this); 
     } 
    } 
}; 

Теперь проблема заключается в том, что в классе кадров вы используете ссылки, которые не могут быть пересаженной, так что вы не можете обновить их. Если ваш случай использование прост, как это обычно вы можете уйти с голым указателем:

class Frame { 
    const Gif* _context; 

    // Make a frame object 
    Frame(const Gif* context) _context(context){...} 

    Frame(const& Frame other) = default; 
    void update_context(const Gif* context) { _context = context; } 
}; 

Почему голый указатель, а не смарт-указатель:

  1. Там нет никаких оснований для кадра, чтобы сделать управление памятью на Gifs, это просто ссылка.
  2. Кажется маловероятным, что у вас будут рамки без контекста Gif. Но даже в этом случае рамка начинается без gif, вы можете просто использовать std::nullptr.
  3. Вы хотите избежать умных указателей, потому что ваши кадры указывают на ваши gifs, которые «указывают» на ваши кадры, которые указывают на ...

Когда вы думаете, что сделать это умным указателем (std::weak_ptr в этом случае), если точка 2 больше не верна. Если кадр начинается с gif, а gif где-то где-то теряется, но в кадре нет, проще использовать weak_ptr, где вы можете проверить, существует ли контекст gif. С голыми указателями вы всегда должны указывать указатель на nullptr, и это более подвержено ошибкам.

+0

Я думаю, что это работает да. как для точки 2. Мало того, что маловероятно, что это крайне нежелательно. Если возможно, я хочу, чтобы компилятор запретил мне совершать эту ошибку. Теперь я считаю, что сам кадр должен стать незаменимым, а вектор должен быть вектором unique_ptr. –

+0

См. Мой ответ. Я предпочитаю это, так как он избегает голого указателя и более безопасен в целом, если только я что-то не упускаю. –

+0

@JoaoTavora, который, конечно же, является хорошим решением, но приводит к совсем другому API, где Frames не являются отдельно конструктивными, это не было оригинальным требованием. – KillianDS

Смежные вопросы