【问题标题】:Access internal object through a proxy object通过代理对象访问内部对象
【发布时间】:2016-03-10 16:50:55
【问题描述】:

这个问题与我的previous question 有关。

假设我们有一个类,它包装一个对象并通过代理返回它:

template <typename T> struct Foo
{
    template <typename ... ARGS>
    Foo(ARGS &&... args) : value(std::forward<ARGS>(args) ...) {}

    struct Proxy
    {
        Proxy(T &v) : value{v} {}

        T *operator->() { return &value; }
        T &operator*() { return value; }
        friend std::ostream &operator <<(std::ostream &o, const Proxy &p) { return o << p.value; }

    private:
        T &value;
    };

    Proxy get() { return {value}; }

private:
    T value{};
};

打算这样使用:

Foo<int> integer{100};
auto integer_proxy = integer.get();
std::cout << integer_proxy << '\n'; // 100

*integer_proxy *= 2;
std::cout << integer_proxy << '\n'; // 200

这个想法是通过调用Foo&lt;T&gt;::get()创建的代理类实例访问包装对象,关于代理类背后的基本原理是保护控制如何访问它的包装对象(例如,使用锁)。但是没有什么能阻止在不使用适当的 Foo&lt;T&gt;::Proxy 实例的情况下访问包装的对象,例如:

Foo<int> integer{100};
std::cout << integer.get() << '\n'; // 100

*integer.get() *= 2;
std::cout << integer.get() << '\n'; // 200

在上面的代码中,Foo&lt;T&gt;::Proxy 对象在每个 Foo&lt;T&gt;::get() 调用中创建和销毁,而不是将其存储在实例中并通过它进行访问。

你试过什么?

我认为当Foo 实例充当右值时重载get() 函数可以解决问题:

template <typename T> struct Foo
{
    // ...
    Proxy get()    { return {value}; }
    Proxy get() && { throw std::logic_error{"forbiden"}; return {}; }
    // ...
};

但是上面的代码编译失败:

error: 'Foo<T>::Proxy Foo<T>::get() &&' cannot be overloaded
     Proxy get() && { throw std::logic_error{"forbidden"}; return {}; }
           ^~~
error: with 'Foo<T>::Proxy Foo<T>::get()'
     Proxy get()    { return {value}; }
           ^~~

经过一些测试和错误后,我设法以这种方式编译代码:

template <typename T> struct Foo
{
    // ...
    Proxy get() &  { return {value}; }
    Proxy get() && { throw std::logic_error{"forbidden"}; return {}; }
    // ...
};

但它也不能防止get() 函数的误用。此外,错误会在运行时引发,而编译时会是更好的选择......但是将 std::logic_error 替换为 static_assert(false, "forbidden") 会导致代码失败,即使未调用静态断言函数:

Foo<int> integer{100};
std::cout << integer.get() << '\n'; // Still valid, no std::logic_error thrown

然后我尝试以与Foo&lt;T&gt;::get()相同的方式更改Foo&lt;T&gt;::Proxy::operator-&gt;(),但将int更改为std::vector&lt;int&gt;(毕竟int没有与此运算符一起使用的成员),然后我已经实现了预期的行为:

template <typename T> struct Foo
{
    // ...
    struct Proxy
    {
        T *operator->() & { return &value; }
        T *operator->() && { throw std::logic_error{"forbidden"}; return nullptr; }
        // ...
    };

    // ...
};

int main()
{
    Foo<std::vector<int>> vector{10, 10};
    auto vector_proxy = vector.get();
    std::cout << vector_proxy->size() << '\n';
    std::cout << vector.get()->size() << '\n'; // logic_error thrown!

    return {};
}

但是,如果我将throw 语句更改为static_assert,即使注释了// logic_error thrown! 行,它也会失败(因此它不会被调用);这也不能防止滥用get() 函数。


问题:

  • 有没有办法实现Foo&lt;T&gt;::get() 函数的预期行为?

  • 为什么用左值引用重载函数会导致编译错误而重载引用和左值引用不会?

    // Error
    struct Err {
        Proxy get()    { ... }
        Proxy get() && { ... }
    }
    
    // Ok
    struct Ok {
        Proxy get() &  { ... }
        Proxy get() && { ... }
    }
    
  • 为什么static_assert在模板类成员函数主体上强制失败会导致编译错误?我一直相信未使用的模板类函数不会被编译(也许只适用于模板函数?)。

【问题讨论】:

    标签: templates c++11 operator-overloading rvalue-reference


    【解决方案1】:

    只需=delete 不希望被调用的重载,就会出现编译时错误。

    Proxy get() && = delete;
    

    或者,在许多情况下,重载 &amp; 而不是 &amp;&amp;

    实际上,如果您想禁止使用临时代理,请在代理而不是代理源上重载 &amp;

        T *operator->()& { return &value; }
        T &operator*()& { return value; }
        friend std::ostream &operator <<(std::ostream &o, const Proxy&&p) = delete;
        friend std::ostream &operator <<(std::ostream &o, const Proxy &p) { return o << p.value; }
    

    仅当存在const&amp; 重载允许使用右值并且您想要阻止它时才使用=delete

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-28
      相关资源
      最近更新 更多