通过代理对象访问内部对象
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()
函数。
问题:
有没有办法实现 Foo<T>::get()
函数的预期行为?
为什么用左值引用重载函数会导致编译错误而重载引用和左值引用不会?
// Error
struct Err {
Proxy get() { ... }
Proxy get() && { ... }
}
// Ok
struct Ok {
Proxy get() & { ... }
Proxy get() && { ... }
}
为什么static_assert
强制在模板class成员函数体上失败导致编译错误?我相信未使用的模板 class 函数不会被编译(也许只适用于模板函数?)。
只需 =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
。
这个问题与我的
假设我们有一个 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()
函数。
问题:
有没有办法实现
Foo<T>::get()
函数的预期行为?为什么用左值引用重载函数会导致编译错误而重载引用和左值引用不会?
// Error struct Err { Proxy get() { ... } Proxy get() && { ... } } // Ok struct Ok { Proxy get() & { ... } Proxy get() && { ... } }
为什么
static_assert
强制在模板class成员函数体上失败导致编译错误?我相信未使用的模板 class 函数不会被编译(也许只适用于模板函数?)。
只需 =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
。