Смарт-указатель, который не может быть нулевым


Иногда мне нужен подсчет ссылок, умный указатель, который не должен быть нулевым. Например, я хочу поделиться очень тяжелый предмет без копирования. И это более удобно разместить его в куче. Так что я мог бы использовать shared_ptr. Кроме того, мне не нужно назначать nullptr, чтобы этот указатель или переназначить его. Поэтому я не хочу проверять и nullptr в код, который его использует. Я думаю, было бы полезно иметь указатель, который не может быть nullptr и не могут быть переназначены.

Я написал небольшой прототип, который имеет эту функцию.

 #include <iostream>
 #include <string>
 #include <memory>
 #include <exception>
 #include <thread>

 using namespace std;

 // Box cannot hold nullptr
 template<class T>
 class Box
 {
 public:  
     template <class... Args>
     static Box<T> create(Args&&... args)
     {
         return Box<T>(make_shared<T>(std::forward<Args>(args)...));
     }

     T * operator-> () const
     {
         return ptr.operator -> ();
     }
 private:
     Box(const shared_ptr<T> & ptr)
         : ptr(ptr)
     {}

     shared_ptr<T> ptr;
 };

 struct Test
 {
     Test(int i, double d, const string & s)
         : i(i), d(d), s(s)
     {
         cout << "Test ctor" << endl;
     }

     ~Test()
     {
         cout << "Test dtor" << endl;
     }

     Test(const Test & test) = delete;
     Test(Test && test) = delete;
     Test & operator = (const Test & test) = delete;
     Test & operator = (Test && test) = delete;

     int i;
     double d;
     string s;
 };

 void print(const Box<Test> & test)
 {
     // there is no need to check for nullptr
     cout << test->i << " " << test->d << " " << test->s << endl;
 }

 void twice(Box<Test> test)
 {
     // there is no need to check for nullptr
     test->i *= 2;
     test->d *= 2;
     test->s += test->s;
 }

 /*
 stdout:
 Test ctor
 42 3.14 hello
 84 6.28 hellohello
 Test dtor
 */
 int main()
 {
     Box<Test> test = Box<Test>::create(42, 3.14, "hello");
     print(test);

     thread t(twice, test);
     t.join();

     print(test);
 }

Этот код правильный? И это верный подход?

Обновление: Похоже, я нашел готовое решение https://github.com/dropbox/nn

Связанные обсуждения:

1) https://groups.google.com/a/isocpp.org/forum/#!тема/СТД-предложения/5TPClBA1fs8

2) http://boost.2283326.n4.nabble.com/Why-no-non-null-smart-pointers-td2642959.html

3) https://lists.boost.org/Archives/boost/2013/10/206732.php



499
5
задан 31 марта 2018 в 08:03 Источник Поделиться
Комментарии
2 ответа

Ну, в принципе, имеющих ненулевые указатели, умный или нет, это хорошая вещь. Это просто позор, что значения NULL не необязательный по умолчанию черта (не только указатели, но и для всех типов номеров номера-zeroability может быть интересным собеседнику-черта, который, кстати, делает более эффективным).

Ваша реализация ее оставляет что-то нужное, потому что вы полностью блокировать преобразование "нормальных" указатели "пустого" ваш необнуляемый и обратно. Что сильно ограничивает ваши коммунальные типов.
Кроме того, можно ожидать гораздо больше возможностей из Указателя-как тип, как больше обращений, больше операторов, и остальные смарт-указатель-интерфейс, по крайней мере, насколько это совместимо с конструкцией-целей.

Теперь давайте посмотрим на код вашего прототипа:


  1. Отдельный свой новый смарт-указатель на тест-код, и сделать его самодостаточным только заголовков библиотеки.

  2. Никогда не используйте using namespace std; в Include-файле, не в том, что он гораздо лучше в исходный файл. Читать "Почему “с помощью пространства имен std;” считается плохой практикой?" по причинам.

  3. Как было сказано выше, для этого нужна куча более аксессоры, и все остальные functons, член и бесплатная, которая включает интерфейс для смарт-указатель.

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

И код вашего теста:


  1. Только явно очищать поток, если вам действительно нужно. Делаю так фривольно цитата дорогостоящим.

  2. Если вы хотите вывести один символ в поток, рассмотреть возможность сделать это прямо вместо с длиной-1 C-стринги. Это может быть немного более эффективным.

6
ответ дан 31 марта 2018 в 10:03 Источник Поделиться

Я написала, как комментарий, что, казалось, можно вместо этого использовать ссылки. Этот ответ показывает, что это может выглядеть немного более подробно.

Использовать ссылки, когда указатели не могут быть nullptr

Как вы знаете, ссылка должна указывать на реальный объект и не может быть nullptrтак кажется, что везде у вас Box<Test>можно просто заменить Test:

void print(const Test & test)
{
// there is no need to check for nullptr
std::cout << test.i << " " << test.d << " " << test.s << std::endl;
}

void twice(Test& test)
{
// there is no need to check for nullptr
test.i *= 2;
test.d *= 2;
test.s += test.s;
}

Использовать std::ref где нужно

Потому что мы сейчас, используя ссылку в качестве аргумента twice()мы должны убедиться, что мы используем std::ref при создании резьбы:

int main()
{
Test test{42, 3.14, "hello"};
print(test);

std::thread t(twice, std::ref(test));
t.join();

print(test);
}

Используйте "любопытно повторяющийся шаблон шаблон" при необходимости

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

template <typename T>
struct Announcer {
Announcer() { std::cout << typeid(T).name() << " ctor\n"; }
~Announcer() { std::cout << typeid(T).name() << " dtor\n"; }
};

Это требует #include <typeinfo> и, возможно, небольшое пояснение. В typeid(T).name() возвращает реализацию определенных имя типа T. С помощью GCC, это искаженное имя класса. Вот как использовать его:

struct Test : Announcer<Test>
{
Test(int i, double d, const std::string & s)
: i(i), d(d), s(s) {}

Test(const Test & test) = delete;
Test(Test && test) = delete;
Test & operator = (const Test & test) = delete;
Test & operator = (Test && test) = delete;

int i;
double d;
std::string s;
};

Обратите внимание, что Test код довольно простой и в противном случае без изменений от оригинала. Однако, ключ в эту строку:

struct Test : Announcer<Test>

Это часто, кажется, даже для опытных C++ программистов, а чего не должно на самом деле компиляции, но он делает и это очень удобно. Что мы сейчас имеем Test класса на основе шаблонного базового класса Announcer<Test>. Эффект очень похож на свой оригинал код:

4Test ctor
42 3.14 hello
84 6.28 hellohello
4Test dtor

В 4Test в выходной-это искаженное имя. С моей системы Linux, можно перевести, что в фактический код синтаксис путем конвейерной передачи выходных данных c++filt -t. Попробуйте положить Test в пространство имен, и вы увидите, что пространство имен становится частью сообщается.

Это все-таки не совсем понятно, какую проблему вы пытаетесь решить в вашем реальном коде, но я надеюсь, что это дало хоть что-то, о ком думать.

1
ответ дан 1 апреля 2018 в 08:04 Источник Поделиться