【问题标题】:Proxy wrapper classes, operator-> chaining代理包装类,运算符-> 链接
【发布时间】:2014-09-22 13:09:53
【问题描述】:

我发现 this paper 实现了一个 C++ 代理包装类,它允许通过重载 operator-> 在每个方法调用之前和之后执行代码。

在第 11 节,技术中提到可以将包装器类链接到彼此中,例如

#include <iostream>
using namespace std;

template<class T, class Pref, class Suf> class Wrap;
template<class T, class Suf>
class Call_proxy {
  T* p;
  mutable bool own;
  Suf suffix;
  Call_proxy(T* pp, Suf su) :p(pp) , own(true) , suffix(su) { } // restrict creation
  Call_proxy& operator=(const Call_proxy&) ; // prevent assignment
  public:
  template<class U, class P, class S> friend class Wrap;
  Call_proxy(const Call_proxy& a) : p(a.p) , own(true) , suffix(a.suffix) { a.own=false; }
  ~Call_proxy() { if (own) suffix() ; }

  T* operator->() const{ return p;} // error: ‘struct Shared<X>’ has no member named ‘g’
  //T& operator->() const{ return *p;} // error: result of ‘operator->()’ yields non-pointer result
};


template<class T, class  Pref, class Suf>
class  Wrap {
  T& p;
  Pref prefix;
  Suf suffix;
public:
  Wrap(T& x,  Pref pr, Suf su) :p(x) , prefix(pr) , suffix(su) { }
  Call_proxy<T,Suf> operator->() const{ 
      prefix() ; 
      return  Call_proxy<T,Suf>(&p,suffix); 
  }
};

void prefix() { cout << "prefix "; }
void suffix() { cout << " suffix\n"; }
struct Pref { void operator()() const{ cout<< " Pref "; } };
struct Suf { void operator()() const{ cout<< " Suf "; } };

template<class T> struct Shared : public Wrap<T,Pref, Suf> {
   Shared(T& obj) : Wrap<T,Pref, Suf>(obj,Pref() , Suf()) { }
};
template<class T> struct Tracer : public Wrap<T,void(*)() ,void(*)()>  { 
    Tracer(T& x) : Wrap<T,void(*)() ,void(*)()>(x,::prefix,::suffix) { } 
};

class X {
public:
    void g() const{ cout << "g()"; }
};

int main() {// test program
  X x;
  Shared<X> xx(x) ;
  Tracer<Shared<X>> xxx(xx);

  xx->g();
  xxx->g();
  return 0;
}

但这会失败并出现错误error: ‘struct Shared&lt;X&gt;’ has no member named ‘g’。 我阅读了operator-&gt; 重载并理解了问题(Call_proxy 返回一个指针,它不会传播重载,如 here 所述)。

我通过返回引用而不是指针尝试了一些替代方案,但最后我遇到了上面提到的问题,或者operator-&gt;被引用类型(error: result of ‘operator-&gt;()’ yields non-pointer result)调用的问题。

有没有办法做到这一点?正如论文中提到的,我认为应该是可能的。


编辑:两个错误的代码版本。

两个版本相同,只是第 17 行或第 18 行被注释掉了。

【问题讨论】:

  • 请发布匹配的代码/错误,什么成员g
  • @PaulEvans 我添加了 X、Tracer 和 Shared 的实现。 Wrap 和 Call_proxy 很长,从论文中 1:1 复制。但如果有帮助,我可以将它们添加到问题中?
  • 你对这个问题的指导是here
  • 我尽量减少

标签: c++


【解决方案1】:

operator-&gt; 必须返回指针类型或具有重载operator-&gt; 的类类型。当它返回具有重载operator-&gt; 的类类型时,就会发生链接。当它返回一个指针时,链接终止。

如果Call_proxy::operator-&gt; 返回T*,则这些链的评估如下:

   Shared<X> xx(x) ;
// xx->g();
   xx.Wrap<X,Pref,Suf>::operator->()   // Returns Call_proxy<X,Suf>
     .Call_proxy<X,Suf>::operator->()  // Returns X*
     ->X::g();

   Tracer<Shared<X>> xxx(xx);
// xxx->g();
   xxx.Wrap<X,void(*)(),void(*)()>::operator->()  // Returns Call_proxy<X,void(*)(),void(*)()>
      .Call_proxy<X,void(*)()>::operator->()      // Returns Shared<X>*
      ->Shared<X>::g();

第一次调用很好,第二次调用失败,因为链接以Shared&lt;X&gt;* 终止,而Shared&lt;X&gt; 没有g()

如果Call_proxy::operator-&gt; 返回T&amp;,则链的评估如下:

   Shared<X> xx(x) ;
// xx->g();
   xx.Wrap<X,Pref,Suf>::operator->()   // Returns Call_proxy<X,Suf>
     .Call_proxy<X,Suf>::operator->()  // Returns X&
     .X::operator->(); // ???

   Tracer<Shared<X>> xxx(xx);
// xxx->g();
   xxx.Wrap<X,void(*)(),void(*)()>::operator->()  // Returns Call_proxy<X,void(*)(),void(*)()>
      .Call_proxy<X,void(*)()>::operator->()      // Returns Shared<X>&
      .Shared<X>::operator->()                    // Call_proxy<X,Suf>
      .Call_proxy<X,Suf>::operator->()            // Returns X&
      .X::operator->(); // ???

两个调用都失败了,因为X 既不是指针类型,也不是重载operator-&gt; 的类类型。

为了使这个设计起作用,Call_proxy::operator-&gt; 需要以某种方式区分T 是否是代理类,如果是则返回T&amp;,如果不是则返回T*

【讨论】:

    【解决方案2】:

    正如我的问题所述,并由@Oktalist 详细说明,问题是区分具有operator-&gt; 的类和没有的类。有两种方法。

    第一个也是最简单的解决方案是有一个受控的层次结构,其中最里面的类总是返回一个指针类型。实现如下所示:

    template<class T> struct Sealed {
      T&p;
      Sealed(T&x ) : p(x){}
      T* operator->() {return &p;}
    };
    

    如果拥有T,则可以使用std::shared_ptr&lt;T&gt;,效果相同。

    int main() {
      X x;
      Sealed<X> xx(x);
      Shared<Sealed<X>> xxx(xx) ;
      Tracer<Shared<Sealed<X>>> xxxx(xxx);
    
      auto y = std::make_shared<X>();
      Shared<decltype(y)> yy(y);
      Tracer<decltype(yy)>yyy(yy);
    }
    

    第二个选项是让Call_proxy 有不同的实现,这取决于类 T 是否有operator-&gt; 的实现。在我看来,这对用户来说更优雅,更不容易出错,但实现起来更复杂。此外,如果没有 C++11 功能,我没有找到实现此功能的方法,很遗憾我无法使用。

    该实现使用 SFINAE 检查器检查某个类是否存在 operator-&gt;

    template <typename T>
    class has_operator
    {
        typedef char yes;
        typedef char no[2];
      template <typename I> static I identity(I);
    
      template<typename U,U> struct Check;
      template <typename C> static yes& test(Check<decltype(identity(&C::operator->)),&C::operator-> >*);
        template <typename C> static no& test(...);
    
    public:
        enum { value = sizeof(test<T>(0)) == sizeof(yes) };
    };
    

    根据结果,Call_proxy 可以是模板专用的;这可以移动到顶级类。

    template<class T, bool hasOperator = true/*has_operator<T>::value*/>
    struct PtrOperator {
      T* p;
      PtrOperator(T*pp) : p(pp) {}
      T* operator->() const { return p;}
    };
    
    template<class T> 
    struct PtrOperator<T,true>{
      T* p;
      PtrOperator<T,true>(T*pp) : p(pp) {}
      T& operator->() const { return *p;}
    };
    
    template<class T, class  Pref, class Suf> class  Wrap;
    template<class T, class Suf>
    class  Call_proxy : public PtrOperator<T>{
      mutable bool own;
      Suf suffix;
      Call_proxy(T* pp, Suf su) : PtrOperator<T>(pp) , own(true) , suffix(su) { }  // restrict creation
      Call_proxy& operator=(const  Call_proxy&) ;  // prevent assignment
    public:
      template<class  U, class  P, class S> friend class  Wrap;
      Call_proxy(const  Call_proxy& a) : PtrOperator<T>(a.p) , own(true) , suffix(a.suffix) { a.own=false; }
      ~Call_proxy() { if (own) suffix() ; }
    };
    

    使用它,最终不需要特殊的类。可以包装任何对象,operator-&gt; 的链接适用于所有情况。

    int main() {
      Shared<X> z(x) ;
      Tracer<Shared<X>> zz(z);
    
      x.g();
      z->g();
      zz->g();
    }
    

    我已将完整示例上传至http://ideone.com/byQYR1

    【讨论】:

      【解决方案3】:

      你的 Wrapper 操作符-> 被你的 Tracer 类隐藏在默认函数中。

      您可以按照此处所述解决此问题: Why does an overloaded assignment operator not get inherited?

      (您也可以尝试在 Wrap 类中将 operator-> 函数设置为 virtual,但我不确定这是否可行。)

      【讨论】:

      • 我认为没有默认的operator-&gt; 可以隐藏基类的实现。我已经测试过here,但它没有任何区别。问题是operator-&gt;返回一个指针类型,它会停止传播
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-09-26
      • 2021-08-16
      • 2017-05-24
      • 2016-02-01
      • 2016-04-14
      • 1970-01-01
      • 2019-09-27
      相关资源
      最近更新 更多