通过代理对象访问内部对象

Access internal object through a proxy object

这个问题与我的有关。

假设我们有一个 class 包装一个对象并通过代理 returns 它:

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<T>::get() 创建的代理 class 实例仅 访问包装对象,关于代理背后的基本原理 class,是为了 protect 控制其访问方式的包装对象(例如,使用锁)。但是没有什么可以阻止在不使用适当的 Foo<T>::Proxy 实例的情况下访问包装对象,例如:

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

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

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

你试过什么?

我认为当 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<T>::get() 相同的方式更改 Foo<T>::Proxy::operator->(),但将 int 更改为 std::vector<int>(毕竟 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 {};
}

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


问题:

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

Proxy get() && = delete;

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

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

    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& 重载允许使用右值并且您想阻止它时才使用 =delete