-1

Say I am given a long and null-terminated cstring as char* text_ptr. I own text_ptr and I am responsible of free()ing it. Currently, I use text_ptr and free() it each time after use.

I try to improve memory safety a bit by wrapping it in a C++ class so that I can enjoy the benefit of RAII. There could be many ways to achieve it. A naive way is: string text_ptr(text_ptr);. However, by doing so, memory is copied once and I still need to manually free() my text_ptr. It would be better if I can avoid memory copy and free() (as this text_ptr is created frequently, performance could take a big hit if I copy it each time). My current thought:

Is it possible to transfer the ownership of text_ptr to a string text_str? Hypothetically, I do text_str.data() = text_ptr;.

Thanks

D.J. Elkind
  • 367
  • 2
  • 8
  • 1
    [`std::string_view`](https://en.cppreference.com/w/cpp/string/basic_string_view)? [`std::unique_ptr`](https://en.cppreference.com/w/cpp/memory/unique_ptr)? Or considering that you both want a view *and* take over ownership, a simple class that uses a similar interface to `std::string` or `std::string_view` and wraps the string internally in a `std::unique_ptr`? – Some programmer dude Feb 02 '23 at 04:40
  • 1
    Just use `std::string`, but if you want to write your own class use `std::make_unique` to manage your memory. 'malloc/free' should not be used in C++, 'new/delete' only inside datastructures. Transfer of ownership will be very clear with a `std::unique_ptr` too. If you want to implement `text_str.data() = text_ptr;.` just copy the string... there is no way to tell from a naked pointer if you can take ownership (and in current C++, the assumption should be naked pointers are non-owning) – Pepijn Kramer Feb 02 '23 at 04:41
  • Summary you can't just pickup a raw pointer and assume it is safe to take over ownership. Just make the copy, because some other code may still have the delete[] call. – Pepijn Kramer Feb 02 '23 at 04:43
  • @PepijnKramer the ownership is guaranteed. the function returns me a raw pointer and explicit says I own it. – D.J. Elkind Feb 02 '23 at 04:45
  • Er, you cannot _assign_ to `text_str.data()` — you would simply lose your data. – Dúthomhas Feb 02 '23 at 04:45
  • @Someprogrammerdude just checked, seems `unique_ptr` fits my need well. – D.J. Elkind Feb 02 '23 at 04:48
  • 1
    Must use `unique_ptr`, not ``. – HolyBlackCat Feb 02 '23 at 05:06
  • @HolyBlackCat: i just tested it, seems it should be `unique_ptr`, not `unique-ptr. – D.J. Elkind Feb 02 '23 at 05:18
  • It depends on which version of the standard you are using. C++17 and later let you say `unique_ptr`. Earlier version required `unique_ptr` with an explicit deleter. – Dúthomhas Feb 02 '23 at 05:20
  • You need to set custom deleter for `std::unique_ptr` for this to work properly. Like `std::unique_ptr data_;` initializing: `data_{ptr, free};` – sklott Feb 02 '23 at 05:21
  • @HolyBlackCat Actually, since data was allocated with `malloc` both `delete` and `delete[]` are wrong, but `delete[]` is "more wrong" than `delete`. – sklott Feb 02 '23 at 05:22
  • @sklott Mhm, you're right. I don't like storing the function pointer though, I'll make a proper answer. – HolyBlackCat Feb 02 '23 at 05:22

2 Answers2

2

std::string can't receive ownership of an external buffer. The best you can do is std::unique_ptr.

By default std::unique_ptr will use delete (or delete[]), but you need std::free(), so a custom deleter is required:

#include <cstdlib>
#include <memory>

struct FreeDeleter
{
    void operator()(void *p) const
    {
        std::free(p);
    }
};

int main()
{
    std::unique_ptr<char[], FreeDeleter> ptr((char *)malloc(42));
}

If you also store the length, you can construct a temporary std::string_view from pointer+length when needed, to conveniently read the string.


Or, a oneliner: std::unique_ptr<char[], std::integral_constant<void(*)(void *), std::free>>.

Another one for C++20: std::unique_ptr<char[], decltype([](void *p){std::free(p);})>.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • My current version is as simple as this: `unique_ptr my_str(get_long_cstr());`. Would it work properly? (I am aware that you provide a deleter, I need some extra time to study what if a deleter is not passed) – D.J. Elkind Feb 02 '23 at 05:29
  • @D.J.Elkind It would cause undefined behavior, because your array expects `free()`, not `delete`. It may or may not work sometimes, but then somebody overrides `new`/`delete` to be incompatible with `malloc`/`free`, and it'll break. – HolyBlackCat Feb 02 '23 at 05:31
  • I see. This is a bit off--my incorrect version `unique_ptr my_str(get_long_cstr());` feels like a drop-in replacement. your correct version is "heavier". I would need to define the `struct FreeDeleter` somewhere, globally, and refer to it each time I need a `unique_ptr`. – D.J. Elkind Feb 02 '23 at 05:37
  • 1
    You can define new type, like `using my_string = std::unique_ptr;` with `FreeDeleter` in one place and then just use `my_string` everywhere. Also, see how asan complains when there is no deleter here: https://godbolt.org/z/nbG5s87j9 – sklott Feb 02 '23 at 05:39
  • @D.J.Elkind Yes, that's the price of correctness. There's also a oneliner version: `std::unique_ptr>`. – HolyBlackCat Feb 02 '23 at 05:53
  • I checked the godbolt link, you enabled `-fsanitize=address` so that the compiler complains. I may need to some to understand what exactly it means... – D.J. Elkind Feb 02 '23 at 06:02
  • for this `std::unique_ptr>`, it makes me think about the way js works (i.e., `()=>free()`), does C++ has something similar (like passing an anonymous function pointer to the 2nd parameter of unique_ptr<>)? – D.J. Elkind Feb 02 '23 at 06:26
  • @D.J.Elkind `std::unique_ptr`, but this requires a fairly modern compiler. – HolyBlackCat Feb 02 '23 at 06:30
  • also, on `std::unique_ptr` vs `std::unique_ptr`? My initial thought is that `unique_ptr` is the "smart" version of `int*`. If this holds, as I am using `char*`, should it be `std::unique_ptr` instead of `std::unique_ptr`? – D.J. Elkind Feb 02 '23 at 06:44
  • @D.J.Elkind `unique_ptr` is supposed to point to a single character, while `unique_ptr` is supposed to point to an array. The former lacks e.g. `operator[]`. – HolyBlackCat Feb 02 '23 at 07:17
0

An idea (not sure it’s a good one, tho)

#include <iostream>
#include <string_view>
#include <cstring>
#include <memory>

struct my_string_view : public std::string_view
{
  using std::string_view::string_view;
  std::shared_ptr<char[]> _data;
  explicit my_string_view( char * data ) 
    : std::string_view(data)
    , _data{data, free} 
    { }
};

void f( const my_string_view & s )
{
  std::cout << "f: \"" << s << "\"\n";
}

int main()
{
  my_string_view s( strdup( "Hello world!" ) );
  f( s );
  std::cout << "main: \"" << s << "\"\n";
}

(This version requires C++17. For older versions of the standard you’ll have to specify the default_deleter<char[]>() explicitly.)

Dúthomhas
  • 8,200
  • 2
  • 17
  • 39