Могут ли умные указатели неявно использоваться в качестве указателей? - PullRequest
1 голос
/ 09 октября 2019

Умные указатели считаются указателями? И, таким образом, могут ли они неявно использоваться в качестве указателей?

Допустим, у меня есть следующий класс:

class MyClass {
    //...
    std::shared_ptr<AnotherClass> foo() { /*whatever*/ };
    void bar(AnotherClass* a) { /*whatever too*/ };
    //...
}

Тогда я могу использовать MyClass следующим образом?

// m is an instance of MyClass
m.bar(m.foo());

Ответы [ 3 ]

4 голосов
/ 09 октября 2019

Нет, их нельзя использовать взаимозаменяемо. Вы получите ошибку компилятора в вашем примере. Но вы всегда можете получить необработанный указатель по shared_ptr::get().

3 голосов
/ 09 октября 2019

НЕТ! Это был бы ужасный API . Да, вы могли бы легко реализовать это в shared_ptr, но только потому, что вы не могли это сделать, не значит, что вам следует.

Почему это такая плохая идея? Интерфейс на основе простого указателя bar не сохраняет экземпляр общего указателя. Если bar где-то сохранит необработанный указатель, а затем завершит работу, нет ничего, что гарантировало бы, что сохраненный указатель не станет висящим в будущем. Единственный способ гарантировать это - сохранить экземпляр общего указателя, а не необработанный указатель (в этом вся суть shared_ptr!).

Становится хуже: следующий код имеет неопределенное поведение, еслиfoo() возвращает экземпляр указателя, который имел только одну ссылку при возврате foo() (например, если foo - простая фабрика новых объектов):

AnotherClass *ptr = m.foo().get();
// The shared_ptr instance returned by foo() is destroyed at this point
m.bar(ptr); // undefined behavior: ptr is likely a dangling pointer here

Вот параметры;сначала рассмотрите перечисленные ранее, прежде чем рассматривать их преемников.

  • Если bar(AnotherClass *) - это внешний API, вам нужно обернуть его безопасным способом, то есть кодом, который вызвал бы Original::bar должен вызывать MyWrapped::bar, а оболочка должна делать все необходимое для управления жизненным циклом. Предположим, что есть startUsing(AnotherClass *) и finishUsing(AnotherClass *), и код ожидает, что указатель останется действительным между startUsing и finishUsing. Ваша оболочка будет выглядеть так:

    class WithUsing {
      std::unique_ptr<AnotherClass> owner; /* or shared_ptr if the ownership is shared */
      std::shared_ptr<User> user;
    public:
      WithUsing(std::unique_ptr<AnotherClass> owner, std::Shared_ptr<User> user) :
        owner(std::move(owner)), user(std::move(user)) {
        user.startUsing(owner.get());
      }
      void bar() const {
        user.bar(owner.get());
      }
      ~WithUsing() {
        user.finishUsing(owner.get());
      }
    };
    

    Затем вы будете использовать WithUsing в качестве дескриптора объекта User, и любое использование будет выполняться через этот дескриптор, обеспечивая существование объекта.

  • Если AnotherClass копируемое и очень дешевое для копирования (например, состоит из одного или двух указателей), передайте его по значению:

    void bar(AnotherClass)
    
  • Если реализация bar не нуждается в изменении значения, может быть определено для получения значения const (объявление может быть без const, так как оно не 'не имеет значения):

    void bar(const AnotherClass a) { ... }
    
  • Если bar не хранит указатель, то не передавайте ему указатель: передайте ссылку на const по умолчанию или не-const ссылка, если необходимо.

    void bar(const AnotherClass &a);
    void bar_modifies(AnotherClass &a);
    
  • Если имеет смысл вызывать bar с "no object" (он же "null"), тогда:

    1. Если передача AnotherClass по значению в порядке, используйте std::optional:

      void bar(std::optional<AnotherClass> a);
      
    2. В противном случае, если AnotherClass вступает во владение, передает unique_ptrработает нормально, так как может бе ноль.

    3. В противном случае передача shared_ptr работает нормально, поскольку может быть нулевой.

  • Если foo() создает новый объект (противвозвращая объект, который уже существует), в любом случае он должен возвращать unique_ptr, не a shared_ptr. Фабричные функции должны возвращать уникальные указатели: это идиоматический C ++. Иначе вводить в заблуждение, поскольку возврат shared_ptr предназначен для выражения существующего общего владения .

    std::unique_ptr<AnotherClass> foo();
    
  • Если bar должен стать владельцем значения, тогда он должен принимать уникальный указатель - это идиома «Я беру на себя управление временем жизни этого объекта»:

    void bar(std::unique_ptr<const AnotherClass> a);
    void bar_modifies(std::unique_ptr<AnotherClass> a);
    
  • Если bar должен сохранить общий доступвладение, тогда это должно занять shared_ptr, и вы будете немедленно конвертировать unique_ptr, возвращенный из foo() в общий:

    struct MyClass {
      std::unique_ptr<AnotherClass> foo();
      void bar(std::shared_ptr<const AnotherClass> a);
      void bar_modifies(std::shared_ptr<AnotherClass> a);
    };
    
    void test() {
      MyClass m;
      std::shared_ptr<AnotherClass> p{foo()};
      m.bar(p);
    }
    

shared_ptr(const Type) иshared_ptr(Type) будет делиться собственностью, они обеспечивают постоянное представление и изменяемое представление объекта соответственно. shared_ptr<Foo> также можно преобразовать в shared_ptr<const Foo> (но не наоборот, для этого вы должны использовать const_pointer_cast (с осторожностью). Вы всегда должны по умолчанию использовать объекты как константы и работать только с непостоянными типамикогда в этом есть явная необходимость.

Если метод не изменяет что-либо, сделайте это самодокументированным, приняв вместо него ссылку / указатель на const something.

2 голосов
/ 09 октября 2019

Умные указатели используются для того, чтобы убедиться, что объект удален, если он больше не используется (ссылка).

Существуют умные указатели для управления временем жизни указателя, которым они владеют /share.

Вы можете вспомнить обертку с указателем внутри. Так что ответ - нет. Однако вы можете получить доступ к указателю, которым они владеют, с помощью метода get().

Обращаем ваше внимание, что при использовании метода get не так сложно создать висячие указатели, поэтому, если вы используете его, будьте особенно осторожны.

...