Используйте карту для нескольких объектов - PullRequest
2 голосов
/ 23 января 2020

Я использую глобальные карты для регистрации одного или нескольких объектов одного типа. Я начал с использования глобального пространства имен для этой цели.

Возьмем это для примера (код не проверен, просто пример):

//frame.h
class frame
{
public:
  frame(int id);
  ~frame();
};

namespace frame_globals
{
  extern std::map<int, frame *> global_frameMap;
};
//frame.cpp
#include "frame.h"
namespace frame_globals
{
  std::map<int, frame *> global_frameMap;
}

frame::frame(int id)
{ 
  //[...]
  //assuming this class is exclusively used with "new"!
  frame_globals::global_frameMap[id] = this;
}

frame::~frame()
{
  frame_globals::global_frameMap.erase(id);
}

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

[РЕДАКТИРОВАТЬ: Кажется, что является неправильным] Переменная члена stati c (насколько мне известно) не вариант, потому что stati c встроен в каждый объект, и мне это нужно для всех объектов.

Каков наилучший способ сделать это? На ум приходит использование общего родителя, но я хочу легкий доступ к своему классу фреймов. Как бы вы решили это?

Или вы знаете лучший способ регистрации объекта, чтобы я мог получить указатель на него откуда-то еще? Так что мне может не понадобиться «новое» и сохранить «это»?

Ответы [ 3 ]

1 голос
/ 23 января 2020

Я бы подумал поменять дизайн. например. что не так с использованием встроенной функции языка?

std::map<int, frame> frames_;

frames_.emplace(id);//adding, ie. creating frames

frames_.erase(id)//removing, ie. deleting frames

Теперь создание / удаление так же просто, как и раньше. По совпадению, если кому-то нужна рамка для другой цели, здесь нет проблем. Если предполагается, что frame это полиморф c, вы можете хранить std::unique_ptr вместо

. Также я хотел бы рассмотреть вопрос о том, чтобы сделать id членом frame, а затем сохранить в std::set вместо std :: map. .

class frame
{
public:
  int id;
  frame(int id);

  friend bool operator <(const frame& lhs, const frame& rhs) {return lhs.id < rhs.id;}
};

std::set<frame> frames_;

Удаление - это просто:

frames_.erase(frame);

Однако теперь поиск по идентификатору несколько сложнее. К счастью, есть решение под рукой. Это требует реализации прозрачного сравнения, которое, среди прочего, включает определение is_transparent.

В целом вам необходимо подумать о владении. Спросите себя: где или кому будет принадлежать / хранить карту / указатели кадров / et c. Храните это там. В случае совместного владения используйте shared_ptr (но используйте только этот шаблон, когда это действительно необходимо - что более редко, чем то, для чего его используют люди).


Некоторые соответствующие основные рекомендации

I.3 Избегать одиночных вызовов

R.5 и R.6 Избегайте неконстантных глобальных переменных

R.11 Избегайте вызова new и явно удаляйте

R.20 и R.21 Предпочитайте unique_ptr перед shared_ptr если вам не нужно делиться собственностью

0 голосов
/ 09 марта 2020

Я хотел бы поставить несколько пунктов на основе кода, размещенного вами.

  1. Как кто-то узнает о frame_globals::global_frameMap и что он делает без просмотра исходного кода.
  2. Если это глобальный объект, тогда frame class logi c может быть обойденным, и глобальный объект может быть изменен извне.
  3. Что если кто-то захочет управлять frame объектами по-другому или если в будущем некоторые требования изменятся, например, если вы хотите разрешить дублирование кадров, то вам придется изменить frame объект.
  4. класс фрейма не только имеет frame связанный элемент данных и функцию-член, но также имеет управляющую логику c. Кажется, он не следует за single responsibility principle.
  5. А как насчет copy/move конструктора? std::map/std::set повлияет на конструкторы класса frame.

Я думаю, что logi c необходимо разделить между frame и способами управления кадрами. У меня есть следующее решение (я не знаю о все варианты использования, мое решение основано на опубликованном коде)

frame класс

  1. frame класс не должен иметь лог c, который не связан с фреймом.
  2. Если объект frame должен знать о других объектах frame, тогда должна быть указана ссылка ContainerWrapper (не напрямую std::map или std::set, чтобы изменение не повлияло frame)

ContainerWrapper класс

  1. ContainerWrapper должен управлять всеми frame объектами.
  2. Может иметь интерфейс согласно требованию, например, try_emplace, который возвращает iterator вставленного элемента ( аналогично std::map::try_emplace), поэтому вместо того, чтобы сначала создать объект frame, а затем попытаться вставить его в std::map, он может проверить наличие id (ключ) и создать объект frame, только если std::map не имеет объектов с указанным id.

О std::map/std::set, std::set может использоваться, если созданный объект кадра не требуется изменять, поскольку std::set не позволит вам изменить кадр, не вынимая его и не вставляя его повторно, или вам придется использовать умный указатель. Таким образом, контейнерный объект может совместно использоваться несколькими frame объектами и не должен быть глобальным объектом.

0 голосов
/ 23 января 2020

В идеале вы должны иметь фабричный шаблон для создания frame объектов. Конструктор frame не должен управлять картой. Это как граждане управляют своими паспортами (в идеале правительство делает это для граждан). Имейте класс менеджера, чтобы держать frame в карте (std::map<int,frame>). Метод, предоставленный классом manager, может создать объект:

class FrameManager
{
    std::map<int,frame> Frames;
public:
    frame CreateFrame(int id) 
    {
           frame new_frame(id);
           Frames[id] =  new_frame;
    }      
};

А как насчет удалений? Ну, в зависимости от вашего дизайна и требований, вы можете иметь либо / оба:

  • Удаление кадров деструктором кадра. Он может вызвать FrameManager::Remove(id);
  • Разрешить удаление также только с помощью FrameManager (FrameManager::Remove(id)). Я бы выбрал это (см. Подробнее ниже).

Теперь обратите внимание, что при таком подходе будет создано много объектов, так что frame (локально, присвоение карте, возврат et c). ). Вы можете использовать shared_ptr<frame> в качестве типа возврата из CreateFrame и оставить shared_ptr в качестве типа карты map<int, shared_ptr<frame>>. Может показаться сложным использовать shared/unique_ptr, но они становятся действительно полезными. Вам не нужно управлять жизнью. Вы можете передать один и тот же shared_ptr<frame> нескольким функциям, не создавая объект frame несколько раз.

Модифицированная версия:

class FrameManager
{
    std::map<int, shared_ptr<frame>> Frames;
public:
    shared_ptr<frame> CreateFrame(int id) 
    {
        if(Frames.count(id)>0) return nullptr;  // Caller can check shared_ptr state

        shared_ptr<frame> new_frame = make_shared<frame>(id);
        Frames[id] =  new_frame;

        return new_frame;
    }      
};

Использование менеджера фреймов позволит полностью контролировать создание фреймов, лучшую обработку ошибок, безопасный / безопасный код, безопасный многопоточный код.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...