0

Having such simple program:

#include <iostream>
#include <string>
#include <windows.h>

using namespace std;
extern char MsgBuff[300];

class MyStr {
    string* strPtr;
public:

    // "normal" constructor
    MyStr(const string& strPtr) : strPtr(new string(strPtr)) {}

    // destructor
    ~MyStr() { 
        if(strPtr != NULL)
        delete strPtr; 
    }

    // copy constructor
    MyStr(const MyStr& x) : strPtr(x.strPtr) {
        OutputDebugStringA("copy constructor");
    }

    // move constructor
    MyStr(MyStr&& x) : strPtr(x.strPtr) {
        x.strPtr = nullptr;
        OutputDebugStringA("copy constructor");
    }

};


int main() {    

    MyStr foo("Exam");
    MyStr foo2 = foo;

    return 0;
}

The program throws an exception: Exception thrown: read access violation. As i invesigated it's caused by the destructor code - destroying these two objects (foo, foo2) we are freeing TWICE the same memory pointed by strPtr pointer.

How can this code be fixed to preserve the logic and avoid the exception?

Derek81
  • 145
  • 9
  • 2
    Why does the copy constructor not `new` it's own string object? These kinds of things are explained as the basics in any good C++ book. – Eljay Aug 30 '20 at 15:31
  • 1
    Why do you allocate and deallocate `std::string`'s? They already do that in the background. Did you mean to allocate a `char*`? – Ted Klein Bergman Aug 30 '20 at 15:35
  • @Eljay Do You mean insted of `strPtr(x.strPtr)` in copy constructor just create a new obj and then siign it to the `x.strPtr`? – Derek81 Aug 30 '20 at 15:37
  • 1
    Copy constructor should allocate a new buffer and copy contents of the source's buffer into it so that you have two objects that are complete copies rather than two objects sharing he same owned resource. – user4581301 Aug 30 '20 at 15:37
  • @user4581301 So insted of strPtr(x.strPtr) in copy constructor just create a new obj and then siign it to the x.strPtr? – Derek81 Aug 30 '20 at 15:39
  • @user4581301 Am i right? – Derek81 Aug 30 '20 at 15:40
  • 1
    Note: To complete [the Rule of Five](https://en.cppreference.com/w/cpp/language/rule_of_three) you should add an assignment operator. [The Copy and Swap Idiom](https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom) will help you get started. – user4581301 Aug 30 '20 at 15:41
  • 1
    `if(strPtr != NULL) delete strPtr;` - That `if` is pointless. `delete nullptr;` is completely valid and specified to do nothing, so just remove that pointless `if` in the destructor. Ohh and stop using `NULL`, use `nullptr`. – Jesper Juhl Aug 30 '20 at 15:41
  • 1
    Handy reading: [What is The Rule of Three?](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) and [Why should C++ programmers minimize use of 'new'?](https://stackoverflow.com/questions/6500313/why-should-c-programmers-minimize-use-of-new) – user4581301 Aug 30 '20 at 15:48
  • Correct: `MyStr(MyStr const& x) : strPtr(new string(x.strPtr != nullptr ? *x.strPtr : string())) { }`. But be aware that in modern C++, you probably will not need to use `new` unless you are making your own smart pointer template class. – Eljay Aug 30 '20 at 16:33

3 Answers3

1

A few things wrong with this code...

MyStr(const MyStr& x) : strPtr(x.strPtr) {
    OutputDebugStringA("copy constructor");
}

This code makes "shallow" copy of the class as it only assigns adresses to existing object instead of creating a new one. This is the main problem, because as main() goes out of scope destructors will be called on all initialized objects. First ~foo will be called. "Succesfully". Then ~foo2 will be called and as it is still a valid object destructor will be called.

if (strPtr != NULL)

will pass, because nowhere in your code do you set strPtr to "nullptr" and so delete on uninitialized object will be called. This will cause the memory access violation.

Few things to keep in mind:

  1. Please use std::string as much as possible. (people that implement this know what they're doing)
  2. Never ever use raw pointers unless absolutely necessary. (this is just realy ugly way of doing things with really no benefits. Use std::shared_ptr and std::unique_ptr instead.)
  3. Always set pointer to NULL after calling delete on it. (This goes without saying. Dont leave objects set to invalid adresses.)
  4. NEVER.. use extern and/or global variables NEVERR!! (This just shows bad code design/structure)
  5. Also this is not "bad" in "main" cpp file, but try to avoid using "using namespace std;". This will save you some headache when working with multiple namespaces.

Now for the "fixed" code part.. I assume you want to do a wrapper for string, so here you go:

#include <iostream>
#include <memory>
#include <string>
#include <windows.h>

class MyStr {
    std::shared_ptr<std::string> m_ptrStr;
public:

    // "normal" constructor
    MyStr(const std::string& strPtr) : m_ptrStr(std::make_shared<std::string>(strPtr)) {}

    // destructor
    ~MyStr() { }

    // shallow copy constructor (you can do this since you have "smart" pointer)
    MyStr(const MyStr& x) : m_ptrStr(x.m_ptrStr) { 
        OutputDebugStringA("copy constructor");
    }

    // move constructor
    MyStr(MyStr&& x) noexcept : m_ptrStr(std::move(x.m_ptrStr)) {
        OutputDebugStringA("move");
    }

};


int main() {

    MyStr foo("Exam");
    MyStr foo2 = foo;

    return 0;
}
  • note that by using a `shared_ptr` all copies are still using the same storage for the string data. Change one copy and you change them all. With regard to point 3, prefer to scope the pointer so that it doesn't exist if it's not pointing to anything. Point 4: Never say never. Reserve globals and externals for the rare cases where it will result in easier to read and maintain code. Doesn't happen often, but it does happen. – user4581301 Aug 30 '20 at 18:10
  • @user4581301 "note that by using a shared_ptr all copies are still using the same storage for the string data.", yes. So does the code from OP (didnt wantt to modify the function of the code). 3, cant do that in a class. 4, well yes, but actually no. If its brand new software and you need golbals/externs it is a bad design. on the other hand if its finished project and you want to implement some feature, then yes, its almost inevitable. – Jiří Jirásek Aug 30 '20 at 18:32
  • Back at you. Point 3 Don't have to do that most of the time. When the object is gone, the values of pointers in the object. For objects containing transient pointer members we're probably going to be in total agreement: use a smart pointer. Point 4: I see it the same as I do `goto`. Avoid it until avoiding it makes your code worse. If there's no case for something, ever, why's it in the language in the first place? – user4581301 Aug 31 '20 at 03:34
  • @user4581301 Its in the language because of backwards compatibility. Bjarne Stroustrup says that himself in "The Essence of C++" and “Learning and Teaching Modern C++” talks. – Jiří Jirásek Aug 31 '20 at 07:32
  • If you're providing a copy constructor, you should also provide an assignment operator, or declare it deleted. – Den-Jason Sep 01 '20 at 08:24
0

The problem in the code is with the copy ctor.

// copy constructor
    MyStr(const MyStr& x) : strPtr(x.strPtr) {
        OutputDebugStringA("copy constructor");
    }

Here you don't create a shallow copy of the string.

The solution is doing one of the following:

  1. If memory and creation time is not an issue, create a deep copy by creating a new string and putting it inside of MyStr.
  2. Using std::shared_ptr<std::String> instead of raw std::string*, which is a bit wasteful in my opinion.
  3. Using copy-on-write approach. On copy, you don't create a new string, but rather another reference to the same resource, and as far as I know, this used to be the approach used by std::string. (you can read more about it on)

Another issue is using std::string since it is already doing exactly what you are trying to accomplish. If you want to make your own implementation, using raw pointers, use char * and not std::string *.

Kerek
  • 1,106
  • 1
  • 8
  • 18
0

Presumably you're doing this to experiment with raw pointers as opposed to needing to house a string.

When using naked pointers, you need to properly implement copy constructors and assignment operators such that you perform a "deep copy" of the addressed data.

Whenever I do this, I write a "clone" method that would in your case perform:

void clone (MyStr const& src)
{
    strPtr = new string(*src.strPtr);
}

Also I would recommend using the "free-and-nil" idiom to avoid double-deletion:

if (srcPtr)
{
    delete srcPtr;
    srcPtr = nullptr;
}

I recommend the Book "Professional C++" by Marc Gregoire which covers this kind of detail. I own copies of the 3rd and 4th editions. https://www.amazon.co.uk/Professional-C-Marc-Gregoire/dp/1119421306

Den-Jason
  • 2,395
  • 1
  • 22
  • 17
  • 1
    free-and-nil won't help here. One object containing the deleted pointer is gone and the copy of the pointer in the other `MyStr` won't know and won't be nulled. If the same `MyStr ` object is deleted twice, *that* is where free-and-nill should have been deployed. But prefer to write code that follows better ownership idioms like [RAII](https://stackoverflow.com/questions/2321511/what-is-meant-by-resource-acquisition-is-initialization-raii) so that double deletes can't happen in the first place. – user4581301 Aug 30 '20 at 15:55
  • 1
    There's no need to test `srcPtr` before deleting it. `delete` knows how to handle null pointers. So `delete srcPtr; strPtr = nullptr;` is sufficient. And, since the code is in the destructor, the pointer is no longer accessible, so there's nothing to gain by nulling it. `delete srcPtr;` is all that's needed. – Pete Becker Aug 30 '20 at 17:19
  • Perhaps in this very limited case yes; but I am talking about idioms to apply in general, since I suspect this example is a contrived one as opposed to any "real" code as such. Hence why I started my answer with "Presumably you're doing this to experiment with raw pointers as opposed to needing to house a string." – Den-Jason Aug 30 '20 at 19:13
  • @PeteBecker I carried the idiom over from Delphi many years ago, but yes it appears you are right here: https://stackoverflow.com/questions/4190703/is-it-safe-to-delete-a-null-pointer – Den-Jason Aug 30 '20 at 19:19