2

我曾尝试在 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() 实现有什么问题吗?谢谢,布谢。

4

3 回答 3

5

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

string CreateSelectStatement() const { return ""; }

并更改 reader 的声明(您的声明应严格解释为 C++ 中的函数原型)

SomeValueReader<A> reader;

上面的例子使用 gcc 编译和执行没有错误,让我相信上面的源代码中可能不存在实际错误。不幸的是,我目前无法使用 VC 进行测试。

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

virtual ~Base() {}

即使它是空的。

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

于 2009-12-11T16:47:27.760 回答
1

我无权访问 Visual Studio,但您的代码的一个问题是 CreateSelectStatement() 未在 A 类中声明为 const。因此它与 Base 和 SomeValueReader 中的其他签名不同。只要您不尝试实例化 A(即,它是一个纯虚拟类,就像 Base 一样),那是可以的。但是您确实在 reader_accessAmemberfunctions_unittest 中实例化了一个。我本来希望您的编译器为此生成一个错误.... g++ 4.4.1 确实如此。可能这不是您的问题;很难说,因为您的示例代码包含其他几个错误。您应该尝试使示例尽可能简单,同时仍然可以编译(例如,它们应该包含您包含的头文件)。您的代码包含多余的语句以及不可编译的伪代码,这使得调试工作更加困难。您将从将代码简化为最简单的形式中学到很多东西,并且通常您随后将能够在不求助于帮助的情况下解决您的问题。这是调试代码的基本步骤。

于 2009-12-11T17:03:08.147 回答
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*,因此在此示例中可能是受保护的且非虚拟的。

于 2009-12-11T19:45:42.080 回答