简体   繁体   中英

Passing a reference to an uninitialised object to a super class constructor and then initialising said object with it's move constructor afterwards?

I am writing an C++ websocket server library. In one of the examples I have provided I use two classes session_base and session . I do this so that I can have the tcp::socket object in the session_base parent class initialised (using a move constructor) before passing a reference to it to the ws::session<tcp::socket> parent class which stores this reference for later use. The reason I have created ws:session as a template class is so i can use boost::asio::ssl::stream s as well as tcp sockets.

Would it be valid to have the tcp::socket object a member of the session class, pass a reference to this uninitialised object to the ws::session constructor (which doesn't use the tcp::socket yet - only stores the reference) and then initialise the tcp::socket object afterwards using the socket move constructor?

Current code:

using boost::asio::ip::tcp;

class session_base {
public:
    session_base(tcp::socket socket) : socket_(std::move(socket)) { }
protected:
    tcp::socket socket_;
};

using T = tcp::socket;
class session : public session_base, public ws::session<T> {
public:
    session(tcp::socket socket) :
        session_base(std::move(socket)), ws::session<T>(socket_)
    {
        std::cout << "session()\n";
    }

    ~session() {
        std::cout << "~session()\n";
    }

private:
    void on_open() override {
        std::cout << "WebSocket connection open\n";
    }

    void on_msg(const ws::message &msg) override {
        /* Do stuff with msg */

        read();
    }

    void on_close() override {
        std::cout << "WebSocket connection closed\n";
    }

    void on_error() override {
        std::cout << "WebSocket connection error\n";
    }
};

Proposed code:

The proposed code below works for me but i am wondering this is defined behaviour and recommended.

using boost::asio::ip::tcp;

using T = tcp::socket;
class session : public ws::session<T> {
public:
    session(tcp::socket socket) :
        ws::session<T>(socket_), socket_(std::move(socket))
    {
        std::cout << "session()\n";
    }

    ~session() {
        std::cout << "~session()\n";
    }

private:
    tcp::socket socket_;

    void on_open() override {
        std::cout << "WebSocket connection open\n";
    }

    void on_msg(const ws::message &msg) override {
        /* Do stuff with msg */

        read();
    }

    void on_close() override {
        std::cout << "WebSocket connection closed\n";
    }

    void on_error() override {
        std::cout << "WebSocket connection error\n";
    }
};

Full source: https://github.com/alexyoung91/ws

If ws::session constructor only stores a reference to the socket but does not use the reference to, for example, call the socket member functions or access data members then the program is well-formed. The standard calls this a reference to the allocated storage in 3.8/6 ([basic.life/6]):

Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise, such a glvalue refers to allocated storage (3.7.4.2), and using the properties of the glvalue that do not depend on its value is well-defined. The program has undefined behavior if:

— an lvalue-to-rvalue conversion (4.1) is applied to such a glvalue,

— the glvalue is used to access a non-static data member or call a non-static member function of the object, or

— the glvalue is implicitly converted (4.10) to a reference to a base class type, or

— the glvalue is used as the operand of a static_cast (5.2.9) except when the conversion is ultimately to cv char& or cv unsigned char& , or

— the glvalue is used as the operand of a dynamic_cast (5.2.7) or as the operand of typeid .

Note that the storage must be allocated when the reference is obtained, and that is so when session constructor is invoked.

But despite that I wouldn't recommend this approach. Mostly because it is easy to forget in the ws::session constructor that the passed reference refers to a not-yet-initialized object and introduce a bug at a later time. Better use the base-from-member idiom and keep your original code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM