2
\$\begingroup\$

I have built a single header-only memory class (just a C++ wrapper around a dynamically-allocated pointer and a size) that supports copy and move semantics (I hope I implemented them correctly) for a game I'm building. Its use case is to wrap the raw contents of assets imported (from any source) before processing them. So there is:

#include <cstdint>
#include <algorithm>

class Memory
{
    uint8_t *ptr;
    size_t size;

public:
    inline Memory() : ptr(nullptr), size(0) {}
    inline Memory(size_t size) : ptr(new uint8_t[size]), size(size) {}
    inline Memory(const Memory &other) : Memory(other.size)
    {
        std::copy(other.ptr, other.ptr+size, ptr);
    }
    inline Memory(Memory &&other) : Memory() { swap(*this, other); }

    inline Memory& operator=(Memory other)
    {
        swap(*this, other);
        return *this;
    }

    inline ~Memory() { delete[] ptr; }

    inline const void* get_ptr() const { return ptr; }
    inline void* get_ptr() { return ptr; }
    inline size_t get_size() const { return size; }

    friend inline void swap(Memory &m1, Memory &m2)
    {
        using std::swap;
        swap(m1.ptr, m2.ptr);
        swap(m1.size, m2.size);
    }
};

Is there something I forgot to add? Are there any errors? Can it be improved in any way?

\$\endgroup\$
4
  • \$\begingroup\$ Welcome to Code Review! "Are there any errors" What do you mean by this ? And try to edit your title to reflect what your code is doing, something like : Wrapper for raw content of assets (or something similar). \$\endgroup\$
    – Marc-Andre
    Commented Oct 7, 2016 at 14:47
  • \$\begingroup\$ You forgot move assign. \$\endgroup\$ Commented Oct 7, 2016 at 14:54
  • 1
    \$\begingroup\$ @ratchetfreak He does assignment from by-value argument instead... should be no difference with optimization. \$\endgroup\$ Commented Oct 7, 2016 at 15:01
  • \$\begingroup\$ Please do not edit your question to incorporate the feedback you got as it invalidates the answers. I don't have enough reputation to roll back your edit but maybe somebody else will. Please also see “What should I do when someone answers my question?”. \$\endgroup\$
    – 5gon12eder
    Commented Oct 8, 2016 at 21:31

2 Answers 2

2
\$\begingroup\$

Code Review

Namespace

It would be nice to put your code inside its ownnamespace to avoid potentially collisions. Memory would seem like a good general name that would be reused by other people at some point.

Style

This is minor.
But most C++ developers put the * beside the type not the name. This is the reverse of the standard C style which is to put the * beside the name.

    uint8_t *ptr;

C++ is all about the type of the object. Putting the star with the type helps to correctly associate the type as a pointer.

Don't use inline unless you are required.

Anything inside the class declaration also automatically marked inline. Also the keyword inline has little to no affect on the compiler (ie it is ignored by every modern compiler) and its decision to actually inline the code.

    inline Memory() : ptr(nullptr), size(0) {}

The only time it is needed is when a method is defined in a header file included in multiple compilation units. Then it is used by the linker to note that the one definition rule is not being broken.

Constructors

All the constructors are good. Though I did have to double check the copy constructor.

    inline Memory() : ptr(nullptr), size(0) {}
    inline Memory(size_t size) : ptr(new uint8_t[size]), size(size) {}
    inline Memory(const Memory &other) : Memory(other.size)
    {
        std::copy(other.ptr, other.ptr+size, ptr);
    }
    inline Memory(Memory &&other) : Memory() { swap(*this, other); }

BUT: The move constructor should also be marked noexcept (except that your move constructor will not throw). This does not do anything for the class itself. But it allows for optimizations in the standard library if you put your class in a container.

Example: When resizing a std::vector<Memory> it will use a noexcept move constructor if it exists otherwise it has to fall back to the copy constructor to provide that strong exception guarantee.

Assignment

This is wrong. I missed the copy caused by pass by value in the parameter

    inline Memory& operator=(Memory other)
    {
        swap(*this, other);
        return *this;
    }

The copy assignment operator has the semantics of doing move assignment. I don't think this is a good idea. You might get some surprised people that don't see that coming when there Memory object changes under the noses when they do an assignment.

I would convert this to a copy operator and then create a move assignment operator (don't forget to mark the move assignment as noexcept).

I would still add a move assignment operator (since you have move constructor).

\$\endgroup\$
5
  • \$\begingroup\$ Actually, I tried to implement the copy-swap idiom explained in this post. It uses a by-value constructor so the compiler automatically infers at call site whether it's needed to be a copy or a move. And, actually, that assignment is noexcept because, if there's an error on the copy, the constructor will not be called. Because of that, the copy/move assignment has automatically the right behavior. \$\endgroup\$
    – JoaoBapt
    Commented Oct 8, 2016 at 20:46
  • \$\begingroup\$ And my inline use is what actually happens: it's defined on a header file and used everywhere. \$\endgroup\$
    – JoaoBapt
    Commented Oct 8, 2016 at 20:50
  • \$\begingroup\$ @JoaoBapt The thing is that functions that are defined inside a class or struct definition are already implicitly marked as inline so the keyword is redundant here. (I'm not aware that it does any harm, though.) \$\endgroup\$
    – 5gon12eder
    Commented Oct 8, 2016 at 21:41
  • \$\begingroup\$ Well, I didn't know that. I'll clean up my definition then. Thank you! \$\endgroup\$
    – JoaoBapt
    Commented Oct 8, 2016 at 21:59
  • \$\begingroup\$ @JoaoBapt: Fixed the comment on the assignment you are correct. But you misunderstand the comments on noexcept. \$\endgroup\$ Commented Oct 9, 2016 at 1:32
0
\$\begingroup\$

I would like to suggest a quite radical refactoring of your code.

using Memory = std::vector<char>;

The standard library already has everything you need here.

Of course, it might still be a good learning opportunity to implement things yourself but as I understand your question, you're actually trying to get a job done here, rather than intentionally reimplementing existing functionality as an acedemic exercise.

\$\endgroup\$
1
  • \$\begingroup\$ Actually, I thought to use vector<char> before, but it has far more functionality than I need. I need only a pointer and a size field; a place where I can put raw content until it is processed and placed into the specific object. vector<char> would be too much for what I need. \$\endgroup\$
    – JoaoBapt
    Commented Oct 8, 2016 at 22:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.