Как я могу стать владельцем данных C++ std::string char без копирования и сохранения объекта std::string?

Как я могу стать владельцем данных std::string char без копирования и без сохранения исходного объекта std::string? (Я хочу использовать движущуюся семантику, но между разными типами.)

Я использую компилятор C++11 Clang и Boost.

В основном я хочу сделать что-то эквивалентное этому:

{
    std::string s(“Possibly very long user string”);
    const char* mine = s.c_str();

    // 'mine' will be passed along,
    pass(mine);

    //Made-up call
    s.release_data();

    // 's' should not release data, but it should properly destroy itself otherwise.
}

Чтобы уточнить, мне нужно избавиться от std::string: в будущем. Код работает как со строковыми, так и с двоичными данными и должен обрабатывать их в одном и том же формате. И мне нужны данные из std::string, потому что они поступают из другого слоя кода, который работает с std::string.

Чтобы дать больше перспективы, когда я сталкиваюсь с желанием сделать это: например, у меня есть асинхронная оболочка сокета, которая должна иметь возможность принимать как std::string, так и двоичные данные от пользователя для записи. Обе версии записи «API» (принимая двоичные данные std::string или строки) внутренне разрешаются в одну и ту же (двоичную) запись. Мне нужно избегать копирования, так как строка может быть длинной.

WriteId     write( std::unique_ptr< std::string > strToWrite )
{

    // Convert std::string data to contiguous byte storage
    // that will be further passed along to other
    // functions (also with the moving semantics).
    // strToWrite.c_str() would be a solution to my problem
    // if I could tell strToWrite to simply give up its
    // ownership. Is there a way?

    unique_ptr<std::vector<char> > dataToWrite= ??

    //
    scheduleWrite( dataToWrite );
}

void scheduledWrite( std::unique_ptr< std::vecor<char> > data)
{
    …
}

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

Меня интересуют решения для этого конкретного случая (с буфером std::string char) и такого рода проблемы со строками, потоками и подобными общими: советы по перемещению буферов между строками, потоками, стандартными контейнерами и типами буферов.

Я также был бы признателен за советы и ссылки с подходами к проектированию C++ и конкретными методами, когда речь идет о передаче данных буфера между различными API/типами без копирования. Я упоминаю, но не использую потоки, потому что я не уверен в этом вопросе.


person minsk    schedule 02.07.2012    source источник
comment
Вы не можете, потому что вы не можете безопасно восстановить память. В какой-то момент вы должны освободить буфер, так почему бы не оставить строку полностью нажатой, что делает это автоматически?   -  person Alexandre C.    schedule 03.07.2012
comment
Вам лучше написать свою собственную реализацию строки   -  person Gigi    schedule 03.07.2012
comment
std::unique_ptr<char[]> было бы единственным, что допускает что-то подобное.   -  person ildjarn    schedule 03.07.2012
comment
@ildjarn: это именно мой вопрос: как преобразовать unique_ptr‹std::string› в unique_ptr‹char[]› или эквивалент без копирования данных.   -  person minsk    schedule 03.07.2012
comment
@minsk: Вы не можете; вам нужно будет начать с std::unique_ptr<char[]> вместо std::string для начала.   -  person ildjarn    schedule 03.07.2012
comment
@Gigi: как я уже упоминал в своем вопросе, std::string исходит из другого уровня кода, который работает с std::string. Единственное, что я хочу потребовать от этого слоя, это передать право собственности на строку. Таким образом, я могу указать, что std::string выделяется в куче (чтобы иметь его в unique_ptr или эквивалентном), но не для изменения типа данных самого std::string. Проблема состоит в том, чтобы украсть эти данные std::string в мою собственную строку или любую другую реализацию.   -  person minsk    schedule 03.07.2012
comment
@minsk: я думаю, что всем понятен ваш сценарий, но вы его не понимаете -- это невозможно. ;-]   -  person ildjarn    schedule 03.07.2012
comment
Кроме того, вы знаете, что std::string отлично хранит двоичные данные (включая встроенные нули), верно? Вы уверены, что не можете просто продолжать использовать std::string?   -  person ildjarn    schedule 03.07.2012
comment
@minsk : std::string содержит любой char, он не ограничен печатными символами. Если раньше у вас были проблемы, это было связано с тем, что вы использовали std::string::c_str() -- при сохранении двоичных данных вы должны вместо этого использовать std::string::data().   -  person ildjarn    schedule 03.07.2012
comment
@ildjarn Как мне проверить размер данных со встроенными нулями из http://www.cplusplus.com/reference/string/string/size/ , size() Возвращает количество символов в строке. string::length является псевдонимом string::size, возвращая одно и то же значение   -  person minsk    schedule 03.07.2012
comment
@minsk: Э-э, с size() или length() - ни один из них не заботится о встроенных нулях (и использует cppreference, а не cplusplus .com, если вам нужна достоверная информация :-]).   -  person ildjarn    schedule 03.07.2012
comment
@ildjarn Приятно знать. Из спецификаций вообще неясно, особенно почему length() это делает. Но все же вопрос был об унификации и хранении переданных std::string и бинарных данных строки от пользователя. Если я просто решу использовать std::string для обоих внутри, у меня будет противоположная проблема: перемещение в буфере строк (динамически выделяемом пользователем) в мой std::string.   -  person minsk    schedule 03.07.2012
comment
Какой тип данных выделяет пользователь? И можете ли вы заставить их изменить это?   -  person ildjarn    schedule 03.07.2012
comment
@ildjarn Необработанные байтовые данные: new char[] или любая его абстракция. Рассматриваемый модуль должен позволять пользователю отправлять как (с нулевым завершением) std::string, так и необработанные двоичные данные. Я могу выбирать любые конкретные подписи, если это позволяет пользователю передавать эти два типа в зависимости от их потребностей, то есть указатель с расположением в куче, содержащей байты, или std::string (std::string также находится в куче). Я не хочу навязывать какую-либо подпись, которая просто перенесет эту проблему с моего уровня на их слой.   -  person minsk    schedule 03.07.2012
comment
@minsk : Опять же, учитывая, что std::string не завершается нулем и может содержать любые символы, включая нетекстовые символы и символы NUL, почему бы и нет просто мандат std::string? В любом случае, если вам это действительно нужно, ваш внутренний код может работать с точки зрения boost::variant<std::unique_ptr<char[]>, std::string> (документов ), позволяя пользователю пройти любой из них и не теряя эффективности с вашей стороны (требуется только две реализации внутри посетителя).   -  person ildjarn    schedule 03.07.2012
comment
@ildjarn Только что проверил, вы ошибаетесь насчет std::string, и длина, и размер возвращают количество символов до первого встроенного нуля. Проверьте это на себе. И не стоит давать ответы, если вы не пробовали и не знаете: std::string s(\a\0b); size_t тест = s.length(); size_t testSize = s.size();   -  person minsk    schedule 03.07.2012
comment
PS: тест == testSize == 1   -  person minsk    schedule 03.07.2012
comment
@minsk : стандарт C++ говорит, что вы ошибаетесь; §21.4.4/1: Возвращает: количество символоподобных объектов, находящихся в данный момент в строке. Вы не должны полагаться на одну реализацию для правильного поведения, вы должны полагаться на стандарт для мандатного поведения; реализации стандартных библиотек тоже содержат ошибки!   -  person ildjarn    schedule 03.07.2012


Ответы (3)


Как я могу стать владельцем данных std::string char без копирования и без сохранения исходного объекта std::string? (Я хочу использовать движущуюся семантику, но между разными типами)

Вы не можете сделать это безопасно.

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


Если вы хотите избежать копирования, вы можете рассмотреть возможность изменения интерфейса на ScheduleWrite. Одна возможность что-то вроде:

template<typename Container>
void scheduledWrite(Container data)
{
    // requires data[i], data.size(), and &data[n] == &data[0] + n for n [0,size)
    …
}

// move resources from object owned by a unique_ptr
WriteId write( std::unique_ptr< std::vector<char> > vecToWrite)
{
    scheduleWrite(std::move(*vecToWrite));
}

WriteId write( std::unique_ptr< std::string > strToWrite)
{
    scheduleWrite(std::move(*strToWrite));
}

// move resources from object passed by value (callers also have to take care to avoid copies)
WriteId write(std::string strToWrite)
{
    scheduleWrite(std::move(strToWrite));
}

// assume ownership of raw pointer
// requires data to have been allocated with new char[]
WriteId write(char const *data,size_t size) // you could also accept an allocator or deallocation function and make ptr_adapter deal with it
{
    struct ptr_adapter {
        std::unique_ptr<char const []> ptr;
        size_t m_size;
        char const &operator[] (size_t i) { return ptr[i]; }
        size_t size() { return m_size; }
    };

    scheduleWrite(ptr_adapter{data,size});
}
person bames53    schedule 02.07.2012
comment
@minsk: Вполне разумно хотеть этого, к сожалению, это просто невозможно, потому что класс не предназначен для этого. - person Benjamin Lindley; 03.07.2012
comment
@minsk: вы не знаете, как должен освобождаться буфер. Поскольку release члена нет, вы не можете добиться того, чего хотите, с string. - person Alexandre C.; 03.07.2012
comment
Это хорошие моменты: небольшая оптимизация строк и знание того, как освободить еще один буфер реализации. Как насчет std::stringstream, могу ли я переместить std::string в std::stringstream, который предоставляет свои буферы? Это и стандартные объекты, и std::stringstream знает о std::string.. Мне бы очень хотелось найти решение, которое позволяет избежать копирования и позволяет части кода работать со строками :( - person minsk; 03.07.2012
comment
@Alexandre: я не хочу полностью сохранять std::string, потому что я хочу внутренне унифицировать реализацию для строковых или двоичных данных. В противном случае я должен отслеживать две версии. - person minsk; 03.07.2012
comment
@minsk: А как насчет std::stringstream, могу ли я как-то переместить std::string в стандартный поток? Нет, std::basic_stringbuf<> берет свой строковый аргумент по константной ссылке. - person ildjarn; 03.07.2012
comment
Это очень похоже на предлагаемые классы std::array_ref и std::string_ref для C++1y. - person deft_code; 03.07.2012

Этот класс становится владельцем строки, используя семантику перемещения и shared_ptr:

struct charbuffer
{
  charbuffer()
  {}

  charbuffer(size_t n, char c)
  : _data(std::make_shared<std::string>(n, c))
  {}

  explicit charbuffer(std::string&& str)
  : _data(std::make_shared<std::string>(str))
  {}

  charbuffer(const charbuffer& other)
  : _data(other._data)
  {}

  charbuffer(charbuffer&& other)
  {
    swap(other);
  }

  charbuffer& operator=(charbuffer other)
  {
    swap(other);
    return *this;
  }

  void swap(charbuffer& other)
  {
    using std::swap;
    swap(_data, other._data);
  }

  char& operator[](int i)
  { 
    return (*_data)[i];
  } 

  char operator[](int i) const
  { 
    return (*_data)[i];
  } 

  size_t size() const
  {
    return _data->size();
  }

  bool valid() const
  { 
    return _data;
  }

private:
  std::shared_ptr<std::string> _data;

};

Пример использования:

std::string s("possibly very long user string");

charbuffer cb(std::move(s)); // s is empty now

// use charbuffer...
person Gigi    schedule 02.07.2012
comment
Насколько я понимаю, перемещаемый charbuffer будет содержать пустой shared_ptr (тот же самый, который по умолчанию создается в конструкторе копирования-перемещения), поэтому, когда перемещенный charbuffer выходит из области видимости и вызывается его деструктор, ничего не происходит. - person Gigi; 03.07.2012
comment
Ты прав на 100%, не знаю, о чем я сейчас думал. :-P Извините за шум. - person ildjarn; 03.07.2012
comment
Сектор перемещения charbuffer(std::string&& str) на самом деле не перемещается из строки. Вам не хватает вызова std::move при инициализации. Должно быть _data(std::make_shared<std::string>(std::move(str))). - person TimeS; 16.07.2019

Вы можете использовать полиморфизм, чтобы решить эту проблему. Базовый тип — это интерфейс к реализации вашего унифицированного буфера данных. Тогда у вас будет два производных класса. Один для std::string в качестве источника, а другой использует ваше собственное представление данных.

struct MyData {
    virtual void * data () = 0;
    virtual const void * data () const = 0;
    virtual unsigned len () const = 0;
    virtual ~MyData () {}
};

struct MyStringData : public MyData {
    std::string data_src_;
    //...
};

struct MyBufferData : public MyData {
    MyBuffer data_src_;
    //...
};
person jxh    schedule 02.07.2012
comment
user315052 Я отметил этот ответ, потому что это решение, и спасибо за ответ. Но я бы избегал этого подхода по ряду причин, включая возможное виртуальное наследование, безопасность типов, проблемы с управлением; в дальнейшем он накладывает тип данных (MyData). Может стать очень громоздким. Мне нужно будет иметь какой-то уникальный доступ к data_src_, кроме того, мне нужно будет создать новые MyData и обернуть их (чтобы передать их, в том числе другим потокам). Если мне нужно использовать оболочку, я бы предпочел использовать менее навязчивый и более безопасный подход без виртуального, предложенный в 1-м ответе bames53. - person minsk; 03.07.2012