c++ - 防止使用重写的非虚函数 - 正确的方法?

标签 c++ inheritance polymorphism virtual-functions pure-function

所以这是我的第一个问题。我搜索了网站,找到了一些东西并应用了其中给出的建议,但我仍然不确定我是否以正确的方式完成了。

我正在开发模板库,这是我对 BST 的实现类模板:

template <class T>
class bstree
{
private:
    struct bstnode
    {
        bstnode* pRight;  //node to the right (greater)
        bstnode* pLeft;   //node to the left (lesser)
        bstnode* pParent; //parent node
        T        mValue;  //contents
    };

    class bstnodeiterator : public _iterator_base<T, bstree<T>>
    {
    public:
        bstnodeiterator(bstnode* pNode = nullptr, bstree<T> pCont = nullptr)
                  : _mpNodePtr(pNode), _mpCont(pCont) {}

        //functions from _iterator_base<>
        bool             is_null() const       { return (_mpNodePtr == nullptr); }
        const bstree<T>* get_container() const { return this->_mpCont; }
        //get_pointer() is intentionally not defined.

        //operators (e.g. increment, decrement, advance by, dereference, etc)
        //go here!
        //...

    private:
        friend class bstree<T>;

        //member elements:
        bstree<T>* _mpCont;    //the container that the iterator is created by
        bstnode*   _mpNodePtr; //the actual pointer pointing to the bst-node of '_mpCont'
    };

public:
    using val      = T;
    using val_ref  = T&;
    using val_ptr  = T*;
    using iter     = bstnodeiterator;

public:
    iter begin() const;
    iter end() const;

    //other public member functions (e.g. insert(), remove(), etc.) go here!
    //...

private:
    bstnode* _mpRoot;   //The root node of the BST
    size_t   _mSize;    //The number of elements in the container (guaranteed O(1))
};

bstnodeiterator::get_container()bstnodeiterator::is_null()源自 iterator_base<>这是所有其他容器的迭代器基类(例如 vector<>fixed_list<>map<> 等):

template <class T, class Cont>
struct _iterator_base
{
    virtual           bool  is_null() const = 0;
    virtual     const Cont* get_container() const = 0;
    /*virtual*/ const T*    get_pointer() const /* = 0*/;
};
//is_null() and get_container() should be defined in derived classes
//because they are used everywhere in the library!
  • 上面的所有三个函数都需要定义,因为它们在整个库的其他地方都用到了(例如算法、iterator_helper 类等)。

由于 BST 是已排序元素的容器,因此不应动态更改节点的内容。因为这会破坏树的排序结构。 因此,我想阻止程序员使用 get_pointer() . 即使它返回指向内容的 const 指针,它仍然可以通过 T 的成员函数进行更改。 (例如,如果 Tstd::string,则可以通过 std::string::assign() 更改内容)我不想要这个。

因此,我编写了函数 _iterator_base<*,*>::get_pointer()基类中的非虚拟。而且它没有在派生类中定义,bstnodeiterator .所以,如果程序员从派生类中调用它...

bstree<std::string> strTree = { "a string", "another string", "yet another string", "test string" };
//inserted some other elements
bstree<std::string>::iterator it = strTree.begin();
//*it = "something else"; --> this won't work, because read-only dereferencing is allowed in the class.
it.get_pointer()->assign("something else"); //this will break the tree.

... 那么编译器会给出链接错误:unresolved external symbol " ... ::get_pointer()" .

这是正确的方法吗?你怎么看?

编辑:

我刚刚尝试取消引用和修改:

bstree<std::string> strTree = 
{ 
   "a string", 
   "another string", 
   "yet another string", 
   "test string" 
};

bstree<std::string>::iter it = strTree.begin();
(*it).assign("modified string"); // ----> error!
std::string pB0 = strTree.begin(); // ----> error

const std::string pB = strTree.begin();
pB->assign("modified string"); // ----> error!

...它没有编译。但是,如果我用以下内容更改最后一行:

it.get_pointer()->assign("modified string");

...它编译没有错误,运行并工作!

编辑 2:

我终于找到了问题的根源:typedef

我没有显示 typedef s 在原来的问题中,使它看起来更简单,更容易阅读。在原始代码中,有一个 using val_ptr = T*;bstree<>范围内我正在使用这个 typedefbstnodeiterator范围内:

template <class T>
class bstree
{
public:
    using val = T;
    using val_ref = T&;
    using val_ptr = T*;

private:
    class bstnodeiterator : public _iterator_base<T, bstree<T>>
    {
        //c'tor comes here!

        const val_ptr get_pointer() { return (_mPtr ? &_mPtr->_mVal : nullptr); }
        //...
    };

 //...
 };

如果我按上面给出的方式定义函数,那么我可以调用 std::string::assign()来自 get_pointer() 的返回指针.但是,如果我将函数的返回类型更改为 const val*那我就不能调用string::assign() .

我终于意识到这两种类型是不同的。可能编译器将 const别处。

最佳答案

响应 OP 的第二次编辑:

别名不像宏。

如果你写using PtrType = T*,那么const PtrType实际上就是 等同于T* const,它是指向T对象的const指针,而不是指向const T对象的指针。当使用别名时,总是在顶层添加更多的 cv 限定符。很直观 - 如果 PtrType 是指向 T 的指针,那么 const PtrType 应该是指向 T 的 const 指针.


根据问题,如果您不希望用户调用虚函数,请将其设为 protected , 所以派生类可以实现它,但外部用户不能调用它。


很可能您将返回类型设置为 bstnodeiterator::get_pointer() T*(而不是 const T*)。

你可能遇到了c++的陷阱covariant return types .

  • Both types are pointers or references (lvalue or rvalue) to classes. Multi-level pointers or references are not allowed.

  • The referenced/pointed-to class in the return type of Base::f() must be a unambiguous and accessible direct or indirect base class of (or is the same as) the referenced/pointed-to class of the return type of Derived::f().

  • The return type of Derived::f() must be equally or less cv-qualified than the return type of Base::f().

注意:c++ 引用没有“(或相同)”子句,但添加它是为了与标准保持一致

因此,如果函数覆盖返回类型为 const std::string* 的函数,则 std::string* 是有效的返回类型。

考虑这个例子:

#include <string>

std::string s = "Hello, world";

struct Base {
    virtual const std::string* foo() = 0;
};

struct Derived : Base {
    std::string* foo() override {
        return &s;
    }
};

int main() {
    Derived d;
    d.foo()->assign("You can do this.");
    return 0;
}

以上代码compiles : 您可以修改 d.foo() 指向的字符串,因为它返回一个 std::string*

关于c++ - 防止使用重写的非虚函数 - 正确的方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59011989/

相关文章:

java - 覆盖父类(super class)的 protected 方法

c++ - std::map、多态和删除

c++ - XML 文件中的动态变量和函数调用

c++ - C++ 初级程序员的 while 循环中的 while 循环

c++ - 为什么我的 NamedPipe 用空格分隔我的字符串?

java - super.clone() 操作在派生类中不起作用

c++ - 无法访问派生类中的基变量

java - 使用 Enum 作为功能齐全的对象(像本例一样使用动态调度)是否意味着滥用它或不是一个好的设计?

java - 子类抛出异常的标准是什么

c++ - 光线追踪 GLSL - 位置移动时球体拉伸(stretch)