Angus Comber
Angus Comber

Reputation: 9708

Correct way to use std smart pointers to ensure ptr safety

Is this the correct way to use std smart pointers to ensure ptr safety

This example may not be the best, but I am trying to emulate some real code. The problem I had was in the real code, the communicator pointer was a raw pointer which could be de-allocated at any moment - resulting in a crash in using the pointer.

So I decided to look into std::shared_ptr and std::weak_ptr to see how it should be designed now we have C++11. I use a weak_ptr in the sending code which checks if the ptr is still valid and only then will dereference the ptr. Is this code the correct approach? Any improvements?

#include <memory>
#include <iostream>
#include <string>

class communicator
{
public:
    communicator(const char* name, int comport, int speed) : name_(name), comport_(comport), speed_(speed) { }

    void send(const std::string& s) {
        std::cout << "sending " << s << " using " << name_ << " at " << speed_ << " rate and using com port " << comport_ << '\n';
    }

private:
    const char* name_;
    int comport_;
    int speed_;
};

class sender
{
public:
    sender() {}

    void set_communicator(std::weak_ptr<communicator> comms) {
        comms_ = comms;
    }

    void send(const std::string& s)
    {
        if (auto sh = comms_.lock())
            sh->send(s);
        else
            std::cout << "Attempting to send: " << s << " but ptr no longer exists\n";
    }

private:
    std::weak_ptr<communicator> comms_;
};

int main() {

    sender mysender;

    {
        // create comms object
        std::shared_ptr<communicator> comms(new communicator("myname", 3, 9600));

        mysender.set_communicator(comms);

        mysender.send("Hi guys!");

    }  // comms object gets deleted here

    mysender.send("Hi guys after ptr delete!");
}

Output:

sending Hi guys! using myname at 9600 rate and using com port 3
Attempting to send: Hi guys after ptr delete! but ptr no longer exists

Upvotes: 9

Views: 353

Answers (3)

Erik Alap&#228;&#228;
Erik Alap&#228;&#228;

Reputation: 2713

Also, when designing interfaces/APIs with the standard smart pointers, consider use of the more restricted pointers like unique_pointer.

Such a pointer communicates intent very clearly - e.g. by taking a unique pointer as argument to a function, you clearly tell the user that he is surrendering ownership of the pointed-to resource to the called function.

Upvotes: 1

Werner Erasmus
Werner Erasmus

Reputation: 4076

Is this the correct way to use std smart pointers to ensure ptr safety

in addition to what has been mentioned by decltype_auto, I can just add that the reason for using weak_ptr is amongst others are to prevent cyclic dependencies. You might as well make it shared if that possibility does not exist, which would make the implementation of send less error prone, except if the lifetime of the communication chanel is really temporary.

You could hide the fact that their exists various connections or sessions in the implementation.

Upvotes: 1

decltype_auto
decltype_auto

Reputation: 1736

pointer which could be de-allocated at any moment - resulting in a crash in using the pointer

That's the symptom behind the rationale for a introducing a weak_ptr; thus I'd consider your weak_ptr - based approach right.

What I find debatable, however, is that in conjunction with this

sender() : comms_() {}

void set_communicator(std::weak_ptr<communicator> comms) {
    comms_ = comms;
}

sort-of two-phase construction of the the sender 's internal asset comms_ you don't reset the internal asset's state to after-construction-state once lock() fails in

void send(const std::string& s)

But that's not "wrong" by itself; it's just something that could be considered for the full scale app.

The other thing is that you don't throw (or let throw by the shared_ptr(weak_ptr) ctor (#11)) when lock() fails, but just if-else handle that. I cannot know the requirements of your full-scaled app, but based on the extract you assembled, exception based error handling would improve the design imo.

E.g.:

#include <memory>
#include <stdexcept>
#include <iostream>
#include <string>

class communicator
{
public:
    communicator(const char* name, int comport, int speed) 
        : name_(name), comport_(comport), speed_(speed) { }

    void send(const std::string& s) {
        std::cout << "sending " << s << " using " << name_ << " at " 
                  << speed_ << " rate and using com port " << comport_ 
                  << '\n';
    }

private:
    const char* name_;
    int comport_;
    int speed_;
};

class sender
{
public:
    struct invalid_communicator : public std::runtime_error {
        invalid_communicator(const std::string& s) :
            std::runtime_error(
                std::string("Attempting to send: \"") + s 
                    + "\" but communicator is invalid or not set"
            ) {}
    };  

    sender() : comms_() {}

    void set_communicator(std::weak_ptr<communicator> comms) {
        comms_ = comms;
    }

    /* non-const */
    void send(const std::string& s) throw (invalid_communicator)
    {
        try {
            auto sh = std::shared_ptr<communicator>(comms_);
            sh->send(s);
        } catch (const std::bad_weak_ptr& e) {
            comms_ = decltype(comms_)();
            throw invalid_communicator(s);
        }
    }

private:
    std::weak_ptr<communicator> comms_;
};

int main() {
    int rv = -1;
    sender mysender;

    for (auto com : {1, 2, 3}) {
        try {
            { 
                // create comms object
                auto comms = std::make_shared<communicator>(
                    "myname", com, 9600
                );
                mysender.set_communicator(comms);
                mysender.send("Hi guys!");
            }// comms object gets deleted here

            mysender.send("Hi guys after ptr delete!"); 

            // never reached in this example; just to illustrate
            // how the story could continue  
            rv = EXIT_SUCCESS;            
            break; // it'd be not nice to "break", but I did not want to
                   // introduce another state variable
        } catch (const sender::invalid_communicator& e) {
            std::cerr << e.what() << std::endl;
        }
    }

    return rv;
}

live a Coliru's

Upvotes: 4

Related Questions