-2

Right now my class has a constructor, copy constructor and copy assignment operator which all do the same thing at first (allocating memory). The destructor is deallocating the memory.

class Register
{
public:
    Register()
    {
        _trampoline_address = reinterpret_cast<BYTE*>(VirtualAlloc(nullptr, _trampoline_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
        if (_trampoline_address == nullptr)
        {
            throw my_exception("Could not allocate memory for trampoline function.");
        }

        //....
    }
    ~Register()
    {
        if (_trampoline_address != nullptr)
        {
            debug(VirtualFree(_trampoline_address, 0, MEM_RELEASE));
        }
    }
    Register(const Register& other)
    {
        _trampoline_address = reinterpret_cast<BYTE*>(VirtualAlloc(nullptr, _trampoline_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
        if (_trampoline_address == nullptr)
        {
            throw my_exception("Could not allocate memory for trampoline function.");
        }

        //...
    }
    Register& operator= (const Register& other)
    {
        _trampoline_address = reinterpret_cast<BYTE*>(VirtualAlloc(nullptr, _trampoline_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
        if (_trampoline_address == nullptr)
        {
            throw my_exception("Could not allocate memory for trampoline function.");
        }

        //....
    }

private:
    BYTE* _trampoline_address;
    static const int _trampoline_size = 20;
};

I thought about outsourcing the allocation algorithm because I use it 3 times, but I don't want that other instances of the same class type can access that function.

So what would be the proper solution to allocate memory in 3 functions in a RAII class?

  • 1
    _"but I don't want that other instances of the same class type can access that function."_ Do you mean inherited classes? I don't get this restriction, as written in the context. Other than that you can simply provide a private function in the class that is used by all of the constructors. – πάντα ῥεῖ May 20 '15 at 17:11
  • 1
    What exactly do you mean by "outsource"? That word doesn't have a specific meaning in C++. – George Hilliard May 20 '15 at 17:11
  • 1
    You could make it a private function. If you want to use it in the class initializer list you could make it a private static function. – NathanOliver May 20 '15 at 17:13
  • @thirtythreeforty I had trouble translating the word into english. What I mean is that I create a new function that does the allocation for me, Then I call that function from my ctor, copy ctor and copy assignment operator. –  May 20 '15 at 17:13
  • @Tobi_R _"What I mean is that I create a new function that does the allocation for me ..."_ As mentioned you can do this. Did you meet any problems, when trying so? – πάντα ῥεῖ May 20 '15 at 17:15
  • @πάνταῥεῖ That's exactly what I thought about. But If I do that, other instances of my class 'Register' will have access to that function. So they could possibly call that "allocate memory function" on another object. –  May 20 '15 at 17:16
  • 1
    @Tobi_R _"other instances of my class"_ Again: Please elaborate about this! It makes no sense for me. Every instance of your class needs to call that function (it's in the constructor). – πάντα ῥεῖ May 20 '15 at 17:18
  • @Tobi_R I think you are confused on how class member functions work. The member function of a class can modify the values of the object that called it and any instance that was passed to the function. I cant call `foo` on one instance and have it modify another instance unless I pass that other instance to foo. – NathanOliver May 20 '15 at 17:19
  • @NathanOliver Indeed I am confused, but I think the private function mentioned multiple times is the best solution in my case. –  May 20 '15 at 17:26

3 Answers3

2

You could create a private static function to help with the memory allocation:

static BYTE* allocate()
{
    BTYE* ptr = reinterpret_cast<BYTE*>(VirtualAlloc(nullptr, _trampoline_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
    if (ptr == nullptr)
    {
        throw my_exception("Could not allocate memory for trampoline function.");
    }
    return ptr;
}

Then, the constructors can be simplified to:

Register() : _trampoline_address(allocate()) { }

Register(const Register& other) : _trampoline_address(allocate()) { }

However, the copy assignment operator needs a little bit more work. First of all, it's not clear from your posted code, what are the semantics of the assignment operator. Are you supposed to copy the data from the RHS to the LHS? If not, what role does the RHS play in the assignment operation? What are you supposed to do with the memory owned by the LHS?

Register& operator= (const Register& other)
{
   // Prevent messing with memory when dealing with self-assignment.
   if ( this != &other )
   {
      // Deal with the semantics of the operation.
   }
   return *this;
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • The copy assignment operator should allocate new memory on the lhs and copy the data of the rhs to the lhs. I am gonna add that check to the assignment operator! –  May 20 '15 at 17:29
  • 1
    Why do you need to allocate new memory? Why not re-use the memory owned by the object? – R Sahu May 20 '15 at 17:30
  • You are right, I got the purpose of the copy assignment operator wrong. So a simple copy algorithm should probably be enough! –  May 20 '15 at 17:33
  • @Tobi_R Also read this article for the subtleties: http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three – πάντα ῥεῖ May 20 '15 at 17:40
0

You can do one of two things:

  1. Create a private function, then call it in each constructor. This still allows other member functions of the class to call the function after construction, but that should be OK (just don't call it!). Subclasses cannot call a class's private members, so you should be fine.
  2. Use delegated constructors. Perform the allocation in one constructor, then call that constructor from the others. Obviously, nobody can call a constructor explicitly, so your concern should be taken care of. Possible caveat: you will need to use a C++11 compliant compiler.
Community
  • 1
  • 1
George Hilliard
  • 15,402
  • 9
  • 58
  • 96
0
struct Register {
  Register():
    _trampoline_address(allocate())
  {}
  Register(Register const& o):
    Register() // forward to default ctor
  {
    copy_data_from(o);
  }
  ~Register() {
    if (_trampoline_address)
      debug(VirtualFree(_trampoline_address, 0, MEM_RELEASE));
  }
  Register& operator= (const Register& o) {
    if (this != std::addressof(o))
      copy_data_from(o);
    return *this;
  }
private:
  void copy_data_from(Register const& o) {
    Assert(_tranpoline_address);
    // ...
  }
  static BYTE* allocate() {
    return reinterpret_cast<BYTE*>(
      VirtualAlloc(nullptr, _trampoline_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)
    );
  }
  BYTE* _trampoline_address;
  static const/*expr*/ int _trampoline_size = 20;
};

there is only one call to allocate, but I still put it in a static private method because it is messy.

I also wrote copy_data_from, because it will be used twice (once in copy-ctor, once in assignment).

I personally would be tempted to have Register() leave you with an empty buffer, only filled when used. Read functions would have to check for nullptr and allocate() if it was missing: but they have to check for nullptr anyhow. The result is a more efficient move-ctor and move-assign, creating empty arrays (and populating later) is far more efficient, etc.

In this case, allocate() turns out to be more useful. You can even have ensure_allocated(), which initializes _tranpoline_address if nullptr.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Thanks for your answer. I was just wondering why should I even make the allocate function static? –  May 20 '15 at 18:59
  • @Tobi_R because `static` states "I don't consume any state of this object". It makes understanding the function easier. It isn't "why use static", but rather "there is no reason not to use static". – Yakk - Adam Nevraumont May 20 '15 at 19:56
  • okay I get it! So if my allocate_memory would directly change the _trampoline_address variable it shouldn't be static? (I don't want to do this but just for the understanding) –  May 20 '15 at 21:28