Какую стратегию следует использовать при попытке указать на автоматические объекты? - PullRequest
0 голосов
/ 06 февраля 2019

Я разрабатываю относительно большую библиотеку (~ 13000 строк) в качестве библиотеки личных утилит.Он использует исключительно контейнеры STL и интеллектуальные указатели для управления памятью - но теперь я нахожусь в ситуации, когда я вижу движение к обычным указателям из-за очевидного отсутствия очевидного решения в STL.Я бы хотел сохранить современный стиль C ++.

Вот мой mcve :

Struct Foo - структура int -оболочки, отображающая, какие функции-членыбыли вызваны.

struct Foo {
    Foo(int x) {
        this->x = x;
        std::cout << "constructor\n";
    }
    Foo(const Foo& other) {
        this->x = other.x;
        std::cout << "copy constructor\n";
    }
    ~Foo() {
        std::cout << "destructor\n";
    }

    int x;
};

Глядя на main Я создаю автоматический экземпляр struct Foo:

int main() {
    Foo foo{ 0 };
    std::cout << "\nfoo.x : " << foo.x << "\n\n";    
}

output>

constructor

foo.x : 0

destructor

Просто так,- теперь, если я хочу указать на foo и манипулировать его содержимым.


Умные указатели, такие как std::unique_ptr или std::shared_ptr, не достигнут этого эффекта (очевидно!).Использование std::make_unique<T>() (/ std::make_shared<T>()) будет динамически выделять память и копировать только значение:

Foo foo{ 0 };
std::unique_ptr<Foo> ptr = std::make_unique<Foo>(foo);

ptr->x = 2;

std::cout << "\nptr->x : " << ptr->x << '\n';
std::cout << "foo.x  : " << foo.x << "\n\n";

output>

constructor
copy constructor

ptr->x : 2
foo.x  : 0 // didn't change  

destructor
destructor

Так что это не работает.Однако их функция-член void reset(pointer _Ptr = pointer()) noexcept позволяет напрямую назначать указатель:

std::unique_ptr<Foo> ptr;
Foo foo{ 0 };

ptr.reset(&foo);

ptr->x = 2;

std::cout << "\nptr->x : " << ptr->x << '\n';
std::cout << "foo.x  : " << foo.x << "\n\n";

output>

constructor

ptr->x : 2
foo.x  : 2

destructor
destructor //crash

Но сбой!foo get деконструируется нормально, а затем std::shared_ptr также хочет деконструировать свою уже деконструированную разыменованную стоимость.

HEAP [main.exe]: для RtlValidateHeap указан неправильный адрес (...)


Использование указателя const:

Foo foo{ 0 };
Foo* const ptr = &foo;

ptr->x = 2;

std::cout << "\nptr->x : " << ptr->x << '\n';
std::cout << "foo.x  : " << foo.x << "\n\n";

output>

constructor

ptr->x : 2
foo.x  : 2

deconstructor
  • 1 выделение, 1 освобождение.
  • без копирования.
  • фактически манипулирует значением.

Пока указатель кажется лучшим вариантом.


Я просто ищу способ указать на автоматически распределенные объекты.Пока что это кажется удивительно трудным, не возвращаясь к простым указателям.

Я сам предоставил одно решение - использование /*const*/ std::reference_wrapper<T>.(не стесняйтесь поправлять меня по поводу этого решения)

Но я не уверен, какая стратегия лучше.Указатель?std::reference_wrapper может быть?Я сделал ошибку с использованием std::shared_ptr / std::unique_ptr?

Есть ли лучший, более простой класс STL?

Ответы [ 3 ]

0 голосов
/ 06 февраля 2019

Немного хеджирую мои ставки на этот счет, так как ваша цель неясна.


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

Вы уже обнаружили, что это в значительной степени несовместимо с объектами автоматического хранения (то, что вы назвали «в стеке»).Вы можете обойти это с помощью пользовательского безоперационного удаления, но, если честно, почему?Ты просто делаешь жизнь слишком сложной для себя.Когда вы создаете объекты таким образом, для вас выбираются владельцы и время жизни, и их легко ошибиться (висячие указатели, ребята!)* s для вас, и используйте полученный интеллектуальный указатель обычным способом.


Однако, очевидно, мы не можем видеть ваш дизайн, и мы не знаем о нем много.

Если всевам нужно передать ссылки на объекты в функции, которые сделают что-то , затем вернутся, а затем просто сделают это!

void bar(const Foo& foo)
{
    // do stuff
}

int main()
{
   Foo foo;
   bar(foo);
}
0 голосов
/ 06 февраля 2019

Но я не уверен, какова лучшая стратегия.Указатель?

Возможно.Если вам нужно указать на объект, тогда указатель может быть хорошим выбором.

Ссылка может быть другой:

Foo foo{ 0 };
Foo& ref = foo;
ref = 2;

Если ссылка достаточна, то обычно предпочтительнее указатель.

Я допустил ошибку при использовании std::shared_ptr / std::unique_ptr?

Да.Вы вступили во владение автоматической переменной.Это нет-нет.

В первом примере вы сделали копию объекта, ожидая, что изменение копии повлияет на оригинал.

0 голосов
/ 06 февраля 2019

std::reference_wrapper:

Foo foo{ 0 };
std::reference_wrapper<Foo> ref = foo;

ref.get().x = 2;

std::cout << "\nref.x : " << ref.get().x << '\n';
std::cout << "foo.x : " << foo.x << "\n\n";

output>

constructor

ref.x : 2
foo.x : 2

deconstructor

, который ведет себя как указатель.Что ж, если посмотреть на его реализацию, то является указателем:

template<class _Ty>
    class reference_wrapper
        : public _Weak_types<_Ty>::type
    {
public:
    /*...*/
private:
    _Ty * _Ptr; //<---
    };

Для эффекта pointer const (Foo* const) его можно объявить как const std::reference_wrapper<Foo> - запретить любое изменение адреса указателя.

Foo foo{ 0 };
const std::reference_wrapper<Foo> ref = foo;
Foo foo2{ 2 };
ref = foo2; //error

Ошибка C2678 двоичная '=': не найден оператор, который принимает левый операнд типа 'const std :: reference_wrapper' (или нет приемлемого преобразования)

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