Home > Back-end >  Is this indirect const access an UB?
Is this indirect const access an UB?

Time:11-15

When I was checking some code today, I noticed an old method for implementing std::enable_shared_from_this by keeping a std::weak_ptr to self in the constructor. Somthing like this:

struct X {
    static auto create() {
        auto ret = std::shared_ptr<X>(new X);
        ret->m_weak = ret;
        return ret;
    }

    // use m_weak.lock() to access the object
    //... 
private:
    X() {}
    std::weak_ptr<X> m_weak;
};

But then something came to me regarding constness of this object. Check the following code:

struct X {
    static auto create() {
        auto ret = std::shared_ptr<X>(new X);
        ret->m_weak = ret;
        return ret;
    }

    void indirectUpdate() const {
        m_weak.lock()->val = 1;
    }

    void print() const {
        std::cout << val << '\n';
    }

private:
    X() {}
    std::weak_ptr<X> m_weak;
    int val = 0;
};

int main() {
    auto x = X::create();
    x->print();
    x->indirectUpdate();
    x->print();
}

In this code, indirectUpdate() is a const method and it should not update our object, but in fact it does. Because std::weak_ptr.lock() returns a non-const shared_ptr<> even though the method is const. So you will be able to update your object indirectly in a const method. This will not happen in case of std::enable_shared_from_this because shared_from_this returns a shared pointer to const ref of object in const method. I wonder if this code is UB or not. I feel it should be, but I'm not sure. Any idea?

Update:

Sorry, it seems that my question was not relayed correctly. I meant even if we have a const pointer, we lose that constness via this method. following code shows that:

struct X {
    static auto create() {
        auto ret = std::shared_ptr<X>(new X);
        ret->m_weak = ret;
        return ret;
    }

    void show() const { std::cout << "const \n";}
    void show() { std::cout << "non-const\n";}

    void indirectUpdate() const {
        show();
        m_weak.lock()->show();
        m_weak.lock()->val = 1;
    }

    void print() const {
        std::cout << val << '\n';
    }

    int val = 0;

private:
    X() {}
    std::weak_ptr<X> m_weak;
};

int main() {
    // Here we have a const pointer
    std::shared_ptr<const X> x = X::create();
    x->print();
    x->indirectUpdate();
    x->print();
}

and output will be following:

0
const 
non-const
1

which shows losing constness.

CodePudding user response:

The object that is modified is not const. There is no undefined behavior.

Add a method like this:

#include <memory>
#include <iostream>

struct X {
    static auto create() {
        auto ret = std::shared_ptr<X>(new X);
        ret->m_weak = ret;
        return ret;
    }

    void show() const { std::cout << "const \n";}
    void show() { std::cout << "non-const\n";}

    void indirectUpdate() const {
        m_weak.lock()->show();
        m_weak.lock()->val = 1;
    }

    void print() const {
        std::cout << val << '\n';
    }

private:
    X() {}
    std::weak_ptr<X> m_weak;
    int val = 0;
};

int main() {
    auto x = X::create();
    x->print();
    x->indirectUpdate();
    x->print();
}

To get this output:

0
non-const
1

Modifying an object via a const mehtod is ok, as long as the method only modifies an object that is actually not const.

It is similar to using a const & to a non-const object. You may cast away constness and modify it as long as the object is really not const:

#include <iostream>

int main() {
    int x = 0;
    const int& ref = x;
    const_cast<int&>(ref) = 42;
    std::cout << x;
}

I also see no danger of misusing the pattern in your code, because once the object is const you won't be able to assign to its val member and all is fine with constcorrectness.


In your Update you have a const pointer in main but the object is still not const. Consider this simpler example:

#include <iostream>

struct foo {
    static foo* create(){
        auto x = new foo();
        x->self = x;
        return x;
    }

    void bar() const {
        this->self->non_const();
    }
    void non_const() {
        std::cout << "Hello World\n";
    }
    foo* self;
};

int main() {
    const foo* f = foo::create();
    f->bar();
    delete f;
}

Its not quite the same as yours, but it has similar effect of calling a non-const method on a seemingly const object. Though its all fine.

The only foo object is that created in foo::create, it is not constant. In main we have a const foo* to that object. main can only call the const member bar. Inside bar the this pointer is a const foo*, but self does not point to a const foo. self itself is `const, but not the object it points to.

  •  Tags:  
  • c
  • Related