从模板参数继承并向上转换回 c++

发布于 2024-08-15 02:55:03 字数 1678 浏览 8 评论 0原文

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

I have tried to use this code in VS2008 (and may have included too much context in the sample...):

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 );
};

This works as expected, i.e. having access to "A" member functions and fooresult returning 1. However, an exception is thrown when objects are deleted at the end of the unittest function:

System.AccessViolationException:
Attempted to read or write protected
memory. This is often an indication
that other memory is corrupt

If I change the return type of Current() function to :

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

then everything is ok and the unit test ends with no access violation. Does someone can tell me what was wrong with the first Current() implementation? Thanks, bouchaet.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

公布 2024-08-22 02:55:03

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

string CreateSelectStatement() const { return ""; }

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

SomeValueReader<A> reader;

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

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

virtual ~Base() {}

即使它是空的,

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

After changing CreateSelectStatement to return a value for the implemented functions (not the pure virtual)

string CreateSelectStatement() const { return ""; }

and changing the declaration of reader (the declaration you have should strictly be interpreted as a function prototype in C++)

SomeValueReader<A> reader;

The above example compiles and executes without errors using gcc, leading me to believe the actual error may not be present in the source above. Unfortunately I'm unable to test with VC at the moment.

I can't see any obvious reason why the change you have suggested would resolve the issue, the only other error I can see is that Base does not have a virtual destructor declared meaning that if you ever delete a Base* (or some other derived class that is not the actual type) the incorrect destructor(s) will fire. You should declare it as

virtual ~Base() {}

even if it's empty.

Stylistically it's also a little odd to use templates and virtual functions in this fashion because here you're using the template to resolve functions at compile time. I can't see a reason why SomeValueReader needs to derive from T (rather than have a member variable) either.

笑看君怀她人 2024-08-22 02:55:03

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

I don't have access to visual studio but one problem with your code is that CreateSelectStatement() is not declared const in class A. So it has a different signature from the others in Base and SomeValueReader. That is ok as long as you do not try and instantiate an A (i.e., it is a pure virtual class just like Base). But you do instantiate one in reader_accessAmemberfunctions_unittest. I would have expected your compiler to generate an error for this.... g++ 4.4.1 does. Possibly this is not your issue; it is hard to tell because your example code contains several other bugs. You should try and keep examples truly as simple as possible while still being compilable (they should contain the header files you include for example). Your code contains superfluous statements as well as uncompilable pseudo code which makes the job of debugging it more difficult. You will learn a lot from reducing your code to its simplest form and often times you will be subsequently able to solve your issue without resorting to asking for assistance. It is a basic step in debugging your code.

红颜悴 2024-08-22 02:55:03

好吧,我的错是我没有尝试从 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*,因此可能受到保护且非虚拟。

Ok, my bad I have not try to compile the code from VS. I was simply typing it and deliberately omit some details. I must say that it was a very bad idea to not test the sample directly and only rely on a behaviour I was seeing in a real project. So a compilable version would be:

/* /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
};

Now, I have to say that I cannot make this sample to produce an access violation. So it is irrelevant. Nevertheless, the modification that I had proposed was the only wordaround I have found to my problem in my real project and I was wondering why.

Item 34: Prefer composition to inheritance
I am well aware of this general guideline and I did wish to define my SomeValueReader as a composition. However, the SomeValueReader do need to access protected functions and members of T to be able to adjust itself to T. To have only a member T would not give enough information to SomeValueReader to perform its responsability. Moreover, SomeValueReader do exploit the Base public non-virtual interface by implementing a set of private virtual functions. Otherwise, it would have to duplicate some code or logic. So, I end up with these options, I either :

  • declare SomeValueReader a friend of Base, duplicate some logic and access T protected members;
  • promote Base protected methods to public (and expose way too much information to all);
  • or try this curious protected inheritance (but does add complexity).

I may have missed another option. Since I could not resolve myself to "cheat" with a friend class, I have decided to go with this template polymorphism. But I am open to suggestions.

The missing const and destructor
The const was an error due to inattention (and to not try to compile the code).
The missing destructor was an omission as I did not see it as a important detail. But few people do think that could have been error. In fact, this is what I would though either. A memory corruption leads us to reach for the error inside the destructors. But in the real project, the Base destructor is in fact public and virtual. Or could have been protected and non-virtual in this example since Base* was not used.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文