2012-07-02 13 views
9

W jaki sposób mogę przejąć na własność dane char std :: string bez kopiowania i z zachowaniem kodu źródłowego std :: string object? (Chcę używać ruchomych semantykę ale między różnymi rodzajami.)Jak mogę przejąć na własność dane C++ std :: string char bez kopiowania i przechowywania obiektu std :: string?

używam C++ 11 Clang kompilator i Boost.

Zasadniczo chcę zrobić coś równoważnego do tego:

{ 
    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. 
} 

Aby wyjaśnić, że trzeba pozbyć się std :: string: dalej w dół drogi. Kod zajmuje się zarówno danymi łańcuchowymi, jak i binarnymi i powinien obsługiwać go w tym samym formacie. I chcę danych z std :: string, ponieważ pochodzi z innej warstwy kodu, która działa ze std :: string.

Aby dać więcej perspektywy, na którą wpadnę, chcąc to zrobić: na przykład mam asynchroniczną wtyczkę do gniazda, która powinna być w stanie pobrać zarówno dane std :: string, jak i binarne od użytkownika do zapisu. Obie wersje zapisu "API" (pobierające std :: string lub dane binarne wiersza) wewnętrznie rozwiązują ten sam (binarny) zapis. Muszę unikać kopiowania, ponieważ ciąg może być długi.

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 w tym przykładzie zilustrować przeniesienie własności: dowolne inne podejście z tymi samymi semantyki jest w porządku ze mną.

Zastanawiam się nad rozwiązaniami dla tego konkretnego przypadku (przy pomocy std :: string char bufora) i tego typu problemami z łańcuchami, strumieniami i podobnymi ogólnymi: wskazówki, jak podejść do poruszania się buforów między ciągami, strumieniami, std kontenerami i buforem typy.

Chciałbym również docenić wskazówki i linki z podejściami do projektowania w C++ i określonymi technikami, jeśli chodzi o przekazywanie danych z bufora pomiędzy różnymi typami/typami API bez kopiowania. Wspominam, ale nie używam strumieni, ponieważ jestem chwiejny w tym temacie.

+1

Nie może, bo nie ma sposobu, można odzyskać pamięć bezpiecznie. W pewnym momencie powinieneś zwolnić bufor, więc dlaczego nie utrzymywać struny w dół, co robi to automatycznie? –

+0

Lepiej napisz własną implementację ciągu znaków: – Gigi

+4

'std :: unique_ptr ' byłoby jedyną rzeczą, która pozwala na coś podobnego. – ildjarn

Odpowiedz

9

W jaki sposób mogę przejąć na własność dane char std :: string bez kopiowania i z zachowaniem obiektu source std :: string? (Chcę używać ruchomych semantykę ale pomiędzy różnymi rodzajami)

Nie można to zrobić bezpiecznie.

Dla konkretnej implementacji i w niektórych okolicznościach można zrobić coś okropnego, np. Użyć aliasowania, aby zmodyfikować prywatne zmienne członkowskie wewnątrz łańcucha, aby oszukać ciąg znaków w myśleniu, że nie jest już właścicielem bufora. Ale nawet jeśli chcesz to wypróbować, to nie zawsze będzie działać. Na przykład. rozważmy małą optymalizację napisów, gdy ciąg nie ma wskaźnika do jakiegoś zewnętrznego bufora przechowującego dane, dane znajdują się wewnątrz samego obiektu napisowego.


Jeśli chcesz uniknąć kopiowania można rozważyć zmianę interfejsu do scheduledWrite. Jedna z możliwości to coś takiego:

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}); 
} 
+1

@minsk: To całkiem rozsądne, aby tego chcieć, niestety po prostu nie jest to możliwe, ponieważ klasa nie jest zaprojektowana, aby na to pozwolić. –

+0

@minsk: Nie wiesz, jak ma zostać zwolniony bufor. Ponieważ nie ma członu "release", nie możesz osiągnąć tego, co chcesz, używając 'string'. –

+0

To są dobre punkty: mała optymalizacja ciągów i umiejętność wydania kolejnego bufora implementacji. Co ze std :: stringstream, czy mogę przenieść std :: string do std :: stringstream, który eksponuje swoje bufory? Są to zarówno obiekty standardowe, jak i std :: stringstream jest świadomy std :: string .. Naprawdę chciałbym znaleźć rozwiązanie, które unika kopiowania i pozwala części kodu na pracę z ciągami :( – minsk

1

Do rozwiązania tego problemu można użyć polimorfizmu. Typ podstawowy jest interfejsem do implementacji zunifikowanego bufora danych. Wtedy miałbyś dwie pochodne klasy. Jeden dla std::string jako źródło, a drugi wykorzystuje własną reprezentację danych.

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_; 
    //... 
}; 
+0

user315052 Oznaczyłem tę odpowiedź, ponieważ jest to rozwiązanie i thx do odpowiadania, ale unikałbym tego podejścia z wielu powodów, w tym możliwego trafienia wirtualnego dziedziczenia, typ bezpieczeństwa, ma zagadnienia związane z zarządzaniem; narzuca rodzaj danych (MyData) na dalszych etapach. Może stać się bardzo uciążliwy. Będę musiał mieć jakiś wyjątkowy dostęp dookoła data_src_, a na dodatek będę musiał użyć nowej MyData i zawinąć ją (aby przekazać ją innym wątkom). Jeśli mam iść z otoką, wolę używać mniej inwazyjnego i bezpieczniejszego podejścia bez wirtualnego, sugerowanego w pierwszej odpowiedzi przez bames53 – minsk

2

Ta klasa przejąć na własność sznurku za pomocą semantyki ruch i 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; 

}; 

Przykład użycia:

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

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

// use charbuffer... 
+0

O ile rozumiem, przenoszony charbuffer będzie trzymał puste shared_ptr (to samo, co jest domyślnie skonstruowany w konstruktorze copy-move), więc gdy przeniesiony charbuffer wyjdzie poza zasięg i jego destruktor zostanie wywołany, nic się nie stanie. – Gigi

+0

Masz 100% racji, nie jestem pewien, co teraz myślę. :-P Przepraszamy za hałas. – ildjarn

+0

Nie ma problemu w ogóle :) – Gigi