【问题标题】:Is this indirect const access an UB?这是间接的 const 访问 UB 吗?
【发布时间】:2022-11-15 04:56:26
【问题描述】:

当我今天检查一些代码时,我注意到了一种实现std::enable_shared_from_this 的旧方法,方法是在构造函数中为self 保留std::weak_ptr。像这样的东西:

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;
};

但是后来我想到了关于这个对象的 constness 的事情。检查以下代码:

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();
}

在这段代码中,indirectUpdate() 是一个 const 方法,它不应该更新我们的对象,但实际上它会更新。因为 std::weak_ptr.lock() 返回一个非 const shared_ptr&lt;&gt;,即使该方法是 const。因此,您将能够在 const 方法中间接更新您的对象。这不会发生在 std::enable_shared_from_this 的情况下,因为 shared_from_this 返回一个共享指针到常量引用const 方法中的对象。我想知道这段代码是否是 UB。我觉得应该是,但我不确定。任何想法?

更新:

抱歉,我的问题似乎没有正确传达。我的意思是即使我们有一个 const 指针,我们也会通过这个方法失去那个 const 性。以下代码显示:

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();
}

输出如下:

0
const 
non-const
1

这表明失去了常数。

【问题讨论】:

  • 您正在修改的对象不是const
  • 它与外部代码没有区别 m_weak.lock()-&gt;val = 1; (忽略私有)

标签: c++


【解决方案1】:

修改的对象不是const。没有未定义的行为。

添加这样的方法:

#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 
";}
    void show() { std::cout << "non-const
";}

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

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

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

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

要获得此输出:

0
non-const
1

通过 const 方法修改对象是可以的,只要该方法只修改实际上不是const 的对象即可。

它类似于对非常量对象使用 const &amp;。只要对象真的不是常量,你就可以放弃常量并修改它:

#include <iostream>

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

我也认为在您的代码中滥用该模式没有危险,因为一旦对象为const,您将无法分配给它的val 成员,并且在 const 正确性的情况下一切都很好。


在您的更新中,您在 main 中有一个 const 指针,但该对象仍然不是 const。考虑这个更简单的例子:

#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
";
    }
    foo* self;
};

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

它与您的不太一样,但它具有在看似const 对象上调用非常量方法的类似效果。虽然一切都很好。

唯一的foo对象是在foo::create中创建的,它不是常量。在 main 中,我们有一个指向该对象的 const foo*main只能呼叫const会员bar。在bar 中,this 指针是const foo*,但self 不指向const fooself 本身是`const,但不是它指向的对象。

【讨论】:

  • 我想我犯了一个错误,我的问题并没有真正传达我的意思。问题是,即使我们有一个 const 对象指针,我们也会在通过 weak_ptr 访问时失去常量性,我想知道这是否是一个 UB。我也更新了问题以反映这一点。
  • @Afshin 它变化不大。更新的输出显示 thisconst 但通过弱指针访问的对象不是
  • @Afshin 更新中的唯一区别是,在 main 中,您有一个 const 指向该对象的指针。但对象本身与旧代码中的相同
  • 但是在访问indirectUpdate() 时,方法对象是 const。我不认为尝试通过在该方法中不是 const 的锁定的 weak_ptr 访问它是正确的。因为在这种情况下,完全编写 shared_from_this() 以从 const 方法返回 const ref 的标准毫无意义。
  • const 方法中的@Afshin this 引用一个 const 对象(当前对象),但这并不意味着对当前对象的任何其他引用也会自动成为对 const 对象的引用。
【解决方案2】:

其他人指出,如果对象实际上是const,则只有UB,这里不是这种情况。


但是您的示例无论如何都与丢弃 constness 无关,它只是通过另一条路线访问它,就像下面的代码一样,它确实有效。

struct X {
    X():non_const_this(this){}
    void update()const{non_const_this->value = 1;}
    int value=0;
    X* non_const_this;
};

void foo(){
    X x;
    x.update();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多