从模板参数继承并在c ++中向上转发

时间:2009-12-11 16:19:26

标签: c++ templates casting polymorphism crtp

我试图在VS2008中使用此代码(并且可能在示例中包含了太多上下文...):

class Base
{
    public:
    void Prepare() {
       Init();
       CreateSelectStatement();
       // then open a recordset
    }
    void GetNext() { /* retrieve next record */ }
    private:
    virtual void Init() = 0;
    virtual string CreateSelectStatement() const = 0;
};
class A : public Base
{
   public:
   int foo() { return 1; }
   private:
   virtual void Init() { /* init logic */ }
   virtual string CreateSelectStatement() { /* return well formed query */ }
};

template<typename T> class SomeValueReader : protected T
{
   public:
   void Prepare() { T::Prepare(); }
   void GetNext() { T::GetNext(); }
   T& Current() { return *this; } // <<<<<<<< this is where it is interesting
   SomeValue Value() { /* retrieve values from the join tables */ }
   private :
   string CreateSelectStatement() const
   {
   // special left join selection added to T statement
   }
};

void reader_accessAmemberfunctions_unittest(...)
{
   SomeValueReader<A> reader();
   reader.Prepare();
   reader.GetNext();
   A a = reader.Current();
   int fooresult = a.foo();
   // reader.foo()            >> ok, not allowed
   Assert::IsEqual<int>( 1, fooresult );
};

这可以正常工作,即可以访问“A”成员函数和fooresult返回1.但是,当在unittest函数结束时删除对象时抛出异常:

  

System.AccessViolationException:   试图读或写受保护   记忆。这通常是一个迹象   其他内存已损坏

如果我将Current()函数的返回类型更改为:

T* Current()
{
   T* current = dynamic_cast<T*>(this);
   return current;
}

然后一切正常,单元测试结束时没有访问冲突。有人能告诉我第一个Current()实现有什么问题吗?谢谢,bouchaet。

3 个答案:

答案 0 :(得分:5)

更改CreateSelectStatement以返回已实现函数(不是纯虚拟)的值

string CreateSelectStatement() const { return ""; }

并更改阅读器的声明(您所拥有的声明应严格解释为C ++中的函数原型)

SomeValueReader<A> reader;

上面的示例使用gcc编译并执行时没有错误,这使我相信上面的源代码中可能不会出现实际错误。不幸的是,我现在无法用VC进行测试。

我看不出任何明显的原因,为什么你建议的更改会解决问题,我能看到的唯一其他错误是Base没有声明虚拟析构函数,这意味着如果你删除了一个Base *(或不正确的析构函数将触发其他一些非实际类型的派生类。你应该声明它为

virtual ~Base() {}

即使它是空的。

在风格上,以这种方式使用模板和虚函数也有点奇怪,因为在这里你使用模板在编译时解析函数。我看不出为什么SomeValueReader需要从T派生(而不是有一个成员变量)。

答案 1 :(得分:1)

我无法访问visual studio,但代码的一个问题是CreateSelectStatement()在类A中没有声明为const。因此它与Base和SomeValueReader中的其他符号具有不同的签名。只要您不尝试实例化A(即,它就像Base一样是纯虚拟类),这是可以的。但是你确实在reader_accessAmemberfunctions_unittest中实例化了一个。我原以为你的编译器会为此生成一个错误.... g ++ 4.4.1。可能这不是你的问题;很难分辨,因为您的示例代码包含其他几个错误。您应该尽可能地保持示例尽可能简单,同时仍然可编译(例如,它们应包含您包含的头文件)。您的代码包含多余的语句以及无法编译的伪代码,这使得调试它的工作变得更加困难。您将从将代码简化为最简单的形式中学到很多东西,而且通常您将来无需求助即可解决问题。这是调试代码的基本步骤。

答案 2 :(得分:1)

好的,我的不好我没有尝试从VS编译代码。我只是输入它并故意省略一些细节。我必须说,不直接测试样本并且只依赖于我在实际项目中看到的行为是一个非常糟糕的主意。所以可编辑的版本是:

/* /clr option enabled */

class Base
{
public:
   void FuncA() {}
protected :
   Base() {}
};

class Derived : public Base
{
public:
   int foo() { return 1; }
};

template<typename T> class SomeValueReader : protected T
{
public:
   void FuncA() { T::FuncA(); }
   T& Current() { return *this; }
};


void main(char* args)
{
   SomeValueReader<Derived> reader;
   reader.FuncA();
   Derived derived;
   derived = reader.Current();
   int fooresult = derived.foo();
   //reader.foo()            >> ok, not allowed
};

现在,我不得不说我不能让这个样本产生访问冲突。所以这是无关紧要的。尽管如此,我提出的修改是我在实际项目中找到问题的唯一方法,我想知道为什么。

第34项:首选合成继承
我很清楚这个一般准则,我确实希望将SomeValueReader定义为一个组合。但是,SomeValueReader确实需要访问受保护的函数和T的成员才能将自身调整为T.只有成员T才能向SomeValueReader提供足够的信息来执行其责任。此外,SomeValueReader通过实现一组私有虚函数来利用Base公共非虚拟接口。否则,它将不得不复制一些代码或逻辑。所以,我最终得到了这些选项,我要么:

  • 将SomeValueReader声明为Base的朋友,复制一些逻辑并访问受保护的T成员;
  • 将基本受保护的方法提升为公开(并向所有人公开过多的信息);
  • 或尝试这种好奇的受保护继承(但确实增加了复杂性)。

我可能错过了另一种选择。由于我无法解决自己与朋友班“欺骗”,我决定采用这种模板多态。但我愿意接受建议。

缺少的const和析构函数
const由于注意力不集中而导致错误(并且不尝试编译代码) 丢失的析构函数是一个遗漏,因为我没有把它看作一个重要的细节。但很少有人认为这可能是错误的。事实上,这也是我想要的。内存损坏导致我们在析构函数内部达到错误。但在实际项目中,Base析构函数实际上是公共的和虚拟的。或者在本例中可能已经受到保护和非虚拟,因为未使用Base *。