-1

I was playing around templates when I was surprised that the code below doesn't work as expected:

#include <iostream>
#include <string>
#include <cstring>

template <class Object>
class CreatorTWO {
public:
  CreatorTWO (void) {}
  ~CreatorTWO (void) throw () {}
  template <typename... Object_Params>
  Object* create (const Object_Params&... params_) {
    Object _temp_obj = Object(params_...);
    size_t _obj_bytes = sizeof (_temp_obj);
    void * _mem_block = std::malloc (_obj_bytes);
    std::memmove(_mem_block,&_temp_obj,_obj_bytes);
    //The line below prints the dereferenced object before it is returned:
    std::cout << *(static_cast <Object*> (_mem_block)) << '\n';
    return static_cast <Object*> (_mem_block);
  }
};

int main (int argc, char* argv[]) {
  CreatorTWO <std::string> _c2;
  std::string* _strp = _c2.create("Hello");
  std::cout << *_strp << '\n';
  std::free (_strp);
  return 0;
}

The code above is supposed to create an object with varying number of parameters passed to it. However, when I created an instance templated with std::string to it, passing an argument of "Hello" should give me a pointer to string that contains "Hello". However, this is not the case. If you run the code above, the pre and post values are different with the pre being the correct one. Does anyone know what causes this undesired behaviour? Thanks.

R34
  • 59
  • 6
  • Don't use `malloc()` with c++. – user0042 Aug 30 '17 at 17:38
  • 3
    You cannot create an object with `malloc` or move it with `memcpy` (or similar functions) unless that object is a [POD type](http://en.cppreference.com/w/cpp/concept/PODType). Further reading : https://stackoverflow.com/questions/146452/what-are-pod-types-in-c `std::string` is most definitively not a POD type. – François Andrieux Aug 30 '17 at 17:38
  • Just because you hacked a custom allocator, doesn't mean it's okay to skip the desturctor call. – StoryTeller - Unslander Monica Aug 30 '17 at 17:39
  • 2
    What is wrong with a simple `return new Object(Object_Params...);`? And why use pointers to begin with? If you *must* use pointers (as an external requirement, it's not needed otherwise) then I recommend you use [`std::unique_ptr`](http://en.cppreference.com/w/cpp/memory/unique_ptr). – Some programmer dude Aug 30 '17 at 17:39
  • *"Does anyone know what causes this undefined behaviour?"* Beware that [undefined behavior](http://en.cppreference.com/w/cpp/language/ub) has a very precise meaning in c++. Undesirable behavior or unexpected behavior are usually used, unless you are specifically referring to the well defined concept of undefined behavior. – François Andrieux Aug 30 '17 at 17:40
  • Thanks for the answers. I am aware that this is not the standard for object creation in c++. I'll read more into POD types in a bit. – R34 Aug 30 '17 at 17:43
  • After reading around POD types. I still don't get why the first call to print would works fine while the second inside main() does not. Anyone care to explain? Thanks. – R34 Aug 30 '17 at 17:56
  • There is no real explanation. Undefined behavior ranges all the way from "sometimes print what is expected" to blow up. Or worse. – Bo Persson Aug 30 '17 at 19:17
  • Ah I see. Thank you for clarifying. – R34 Aug 30 '17 at 21:06

1 Answers1

1

C++ sits in an uncomfortable place between raw access to the underlying hardware, and a high level language which is capable of rapid development.

The general principle is that you can't use memcpy to move an object, which you have broken in this case.

When you create a class.

 class Example {
     protected:
        char * ptr;
        size_t len;
     public:
        Example( const char * str ) {
            len = strlen( str );
            ptr = new char[ len  + 1];
        }
        virtual ~Example() {
             delete [] ptr;
        }
        Example & operator=( const Example & rhs ) {
            if( &rhs != this ) {
                delete [] ptr;
                len = rhs.len();
                ptr = new char[ len + 1 ];
                strcpy( ptr, rhs.ptr );
            }
        }
        Example( const Example & src ) {
            len = src.len;
            ptr = new char[ len + 1];
            strcpy( ptr, src.ptr );
        }
        Example() {
            ptr = new char[1];
            ptr[0] = '\0';
            len = 0;
        }
 };

The idea of accessing the internal state stops the designer of the class from being able to make guarantees of correctness.

If the class Example had been memcpy'ed, then there would be two instances of the class, with both having the value ptr set. If one of them is destroyed, then it free the memory addressed by ptr in the other, breaking the other classes internal state.

When a class has been well implemented, copying using the operator= allows the designer to understand what can be shared, and what needs to be copied, and can ensure correct behavior.

By modifying or copying a class using raw memory operators is heads towards undefined behavior for this reason.

mksteve
  • 12,614
  • 3
  • 28
  • 50