1

I have a class, which contains some allocated pointers (reduced to 1 in the example), and I would like not to have to deep copy when the copy operator is called. The class is the following:

class Example {
    public:
    Example() : ptr1(0), len1(0) {} 
    Example(const Example& other) : ptr1(0), len1(0) {
        if (other.ptr1) {
            ptr1 = other.ptr1;
            len1 = other.len1;
        }
    }
    ~Example() {
        if (ptr1)
            delete[] ptr1;
    }
    char* ptr1;
    int len1;
};

A reasonable use after free happens when I try to create some Example class, assign the pointers, and insert it into a cointainer that has scope outside the function in which assingment and insertion happens :

// some function
{
    Example a;
    a.ptr1 = new char[1];
    vec.push_back(Example); // some std::vector<Example> I created outside the function call.
}

I know by deep copying that this could be solved. Or by inserting an empty Example and then operate with the Example copy that the map saved, but I would like to know if there are more options. The sandard I'm using is C++11.

carce-bo
  • 480
  • 2
  • 10
  • 2
    `std::shared_ptr<>`? – lorro Sep 27 '22 at 09:28
  • Yes, you can use smart pointers to share the data. For example `std::shared_ptr ptr1`. The way you have it right now will cause serious issues if you copy the object, because now you'll have two objects controlling a single pointer, and none of them know about the other. – paddy Sep 27 '22 at 09:28
  • I knew these smart pointers exist, but was unsure of when to use them (I am learning C++ after having learnt C). I will read some documentation and try it, thanks ! – carce-bo Sep 27 '22 at 09:33
  • 1
    If you have a lot of these things to share, consider wrapping them in a struct and maintain that in a single shared pointer. – paddy Sep 27 '22 at 09:47
  • Is `ptr1` supposed to hold a literal pointer to a character kind or indicate string kind (like C). If you mean for it to be a string kind, I highly recommend using [`std::string`](https://en.cppreference.com/w/cpp/string/basic_string) and wrapping that in `std::shared_ptr` if you still need shared access to the string. – oraqlle Sep 27 '22 at 12:44
  • This morning I found out the hard way about bytearrays inside std::shared_ptr and the alloc-dealloc mismatch. I ended up surrendering and using my container with allocated pointers to Example instead of just the class. But I did learn a lot ! – carce-bo Sep 27 '22 at 16:17

2 Answers2

2

Having seen your comment, here is one alternative not included in The Dreams' answer. The simplest way out, as suggested by Sebastian's comment, is to just treat Example as not owning its data at all:

struct Example {
    char* ptr1 = nullptr;
    int len1 = 0;
};

Since this has a default destructor and copy/move functions provided by the compiler, it will not release any allocated memory. However, managing that outside the scope of the class might be the easier solution.

If you do wish to tie the fate of the allocated memory to the class, you could still make use of smart pointers with something along the lines of point 3 in the other answer. The top answer to your cited question shows how you can specify a custom deleter function to a shared pointer. For completeness, an example with std::unique_ptr:

#include <cstdlib>
#include <memory>

class Example {
    std::shared_ptr<char[]> ptr1;
    int len1;

    public:
    template<typename Deleter>
    Example(std::unique_ptr<char[], Deleter>&& p = nullptr, int len = 0)
        : ptr1{std::move(p)}, len1{len} {}
};

struct MyDeleter {
    void operator()(char* p) { free(p); }
};

int main() {
    auto* chars = static_cast<char*>(malloc(sizeof(char)));
    Example a {std::unique_ptr<char[], MyDeleter>(chars), 1};
}
sigma
  • 2,758
  • 1
  • 14
  • 18
  • Should have mentioned it, but the reason I use C pointers is because I recieve them allocated by a propietary C library, and I didn't wanted to wrap all malloc'd pointers to new alloc'd ones (I saw [here](https://stackoverflow.com/questions/13061979/shared-ptr-to-an-array-should-it-be-used) how would a shared pointer work in this case). – carce-bo Sep 29 '22 at 07:20
  • @carce-bo I see! In that case, why not leave the copy operator and destructor undefined and rely on the compiler-generated ones? I'll change my answer to clarify. – sigma Sep 29 '22 at 19:51
  • This answer is exactly what I needed. I read the reference but got lost on syntax (it gets difficult to understand what syntax is necessary and whats optional). Thank you for your answer. – carce-bo Oct 11 '22 at 08:10
1

I know by deep copying that this could be solved. Or by inserting an empty Example and then operate with the Example copy that the map saved, but I would like to know if there are more options

For custom classes only you define what it means for them to be copied.

  1. Copy the owned resources If you consider the class instance should unchallengly own the resource, you better move the allocation part (i.e. any use of new keyword) under the hood of the class implementation, essentially making a handle of the resource out of your class (this way you also follow so-called RAII idiom). Standard library, however, already has handles implemented for such a popular thing as arrays. Depending on your requirements you can choose out of rich collection of container classes, but in case of char arrays you actually better just use std::string:
#include <string>
#include <vector>

class Example {
    std::string resource;
    
public:
    Example(const std::string& str) : resource{ str } {}
    
    Example(const Example& other) : resource{ other.resource } {}

};

int main(int argc, char * argv[]) {
    std::vector<Example> collection;
    collection.emplace_back("test");
    return EXIT_SUCCESS;
}
  1. Move the owned resource provided your class owns the resource, but you don't want to copy it, in C++ you have a luxury of moving semantic. It comes handy when you don't actually need to work with the source instance of the copy, and can save some calculation power by moving the resource instead of making any copy of it. You can even make std containers use your move constructor (instead of copy) when they re-allocate internal space. All you need to do to introduce it for your custom class is to provide a noexcept move constructor:

class Example {
    std::string resource;
    
public:
    Example(const std::string& str) : resource{ str } {}
    
    Example(const Example& other) : resource{ other.resource } {}
    Example(Example&& other) noexcept : resource{ std::move(other.resource) } {}
};
  1. Share the resource If you consider the class instances to "share" resources, i.e. multiple instances can have access to the same resource, then widely advertised std::shared_ptr comes very handy. It takes responsibility for tracking how many pointers exist to the resource and deallocate the resource for you when all such pointers were destroyed:
#include <memory>
#include <vector>

class Example {
    std::shared_ptr<char[]> resource;
    std::size_t length;    
    
public:
    Example(std::unique_ptr<char[]> _resource, std::size_t _length) : resource{ std::move(_resource) }, length{ _length } {}
    
    Example(const Example& other) : resource{ other.resource }, length{ other.length } {}

};

int main(int argc, char * argv[]) {
    std::vector<Example> collection;
    collection.emplace_back(std::make_unique<char[]>(8), 8);
    return EXIT_SUCCESS;
}
The Dreams Wind
  • 8,416
  • 2
  • 19
  • 49
  • Another option would be to manage the resource outside of the class. E.g. the copies are needed for a complex algorithm, after it is finished, delete all objects and separately all resources. The copy of the objects can now be reduced to shallowly copying pointers. It would be much cheaper/faster than shared pointers, even for multi-threaded code. – Sebastian Sep 29 '22 at 08:59
  • @Sebastian using raw pointers explicitly should be avoided as possible. I agree there might be scenarios when the resource can outlive the instances and in this case the 3'rd scenario should not require `unique_ptr` to access such a resource, but you can just switch the constructor arguments from `unique_ptr` to `shared_ptr` (or even `weak_ptr`, if the resource lifecycle is completely independent) – The Dreams Wind Sep 29 '22 at 09:12
  • What would be bad with raw pointers? They would be non-owning in this case. You could alternatively encapsulate the idea in a "const reference class", which stores a const reference at the time of construction in its members and could be freely copied. If from the outside you make sure that the reference does not outlive the referenced object. `shared_ptr` and `weak_ptr` seem overkill in a scenario, where the time and place of destruction is predetermined. – Sebastian Sep 29 '22 at 14:06