防止使用重写的非虚函数 - 正确的方法?
Preventing the use of an overridden non-virtual function - The correct way?
所以这是我的第一个问题。我搜索了网站,找到了一些东西并应用了其中给出的建议,但我仍然不确定我是否以正确的方式完成了。
我正在开发模板库,这是我对 BST class 模板的实现:
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<>
,它是所有其他容器的迭代器基础 class(例如 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
class 等)。
由于 BST 是已排序元素的容器,因此不应动态更改节点的内容。因为这会破坏树的排序结构。 因此,我想阻止程序员使用 get_pointer()
。 即使它 return 是指向内容的常量指针,它仍然可以通过 [的成员函数进行更改=24=](例如,如果 T
是 std::string
,则可以通过 std::string::assign()
更改内容)我不想要这个。
因此,我在基 class 中将函数 _iterator_base<*,*>::get_pointer()
设为非虚函数。并且在派生的 class、bstnodeiterator
中没有定义。所以,如果程序员从派生的 class ...
中调用它
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
s.
我没有在原始问题中显示 typedef
以使其看起来更简单易读。在原代码中,bstree<>
范围下有一个using val_ptr = T*;
,我在bstnodeiterator
范围下使用这个typedef
:
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); }
//...
};
//...
};
如果我按上面给出的方式定义函数,那么我可以从 get_pointer()
的 returning 指针调用 std::string::assign()
。但是,如果我将函数的 return 类型更改为 const val*
那么我就无法调用 string::assign()
.
我终于意识到这两种类型是不同的。可能编译器将 const
放在其他地方。
响应 OP 的第二次编辑:
别名不像宏。
如果写using PtrType = T*
,那么const PtrType
其实就是
等同于 T* const
,它是指向 T
对象的 const 指针,而不是指向 const T
对象的指针。当使用别名时,总是在顶层添加更多的 cv 限定符。很直观——如果 PtrType
是指向 T
的指针,那么 const PtrType
应该是指向 T
.
的常量指针
根据问题,如果你不想让用户调用一个虚函数,就把它做成protected
,这样派生类可以实现它,但外部用户不能调用它。
很可能您制作了 bstnodeiterator::get_pointer()
T*
的 return 类型(而不是 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++参考没有“(或相同)”子句,但添加它是为了与标准保持一致
所以 std::string*
是一个有效的 return 类型,如果该函数覆盖 return 类型 const 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()
指向的字符串,因为它return是一个std::string*
.
所以这是我的第一个问题。我搜索了网站,找到了一些东西并应用了其中给出的建议,但我仍然不确定我是否以正确的方式完成了。
我正在开发模板库,这是我对 BST class 模板的实现:
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<>
,它是所有其他容器的迭代器基础 class(例如 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
class 等)。
由于 BST 是已排序元素的容器,因此不应动态更改节点的内容。因为这会破坏树的排序结构。 因此,我想阻止程序员使用 get_pointer()
。 即使它 return 是指向内容的常量指针,它仍然可以通过 [的成员函数进行更改=24=](例如,如果 T
是 std::string
,则可以通过 std::string::assign()
更改内容)我不想要这个。
因此,我在基 class 中将函数 _iterator_base<*,*>::get_pointer()
设为非虚函数。并且在派生的 class、bstnodeiterator
中没有定义。所以,如果程序员从派生的 class ...
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
s.
我没有在原始问题中显示 typedef
以使其看起来更简单易读。在原代码中,bstree<>
范围下有一个using val_ptr = T*;
,我在bstnodeiterator
范围下使用这个typedef
:
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); }
//...
};
//...
};
如果我按上面给出的方式定义函数,那么我可以从 get_pointer()
的 returning 指针调用 std::string::assign()
。但是,如果我将函数的 return 类型更改为 const val*
那么我就无法调用 string::assign()
.
我终于意识到这两种类型是不同的。可能编译器将 const
放在其他地方。
响应 OP 的第二次编辑:
别名不像宏。
如果写using PtrType = T*
,那么const PtrType
其实就是
等同于 T* const
,它是指向 T
对象的 const 指针,而不是指向 const T
对象的指针。当使用别名时,总是在顶层添加更多的 cv 限定符。很直观——如果 PtrType
是指向 T
的指针,那么 const PtrType
应该是指向 T
.
根据问题,如果你不想让用户调用一个虚函数,就把它做成protected
,这样派生类可以实现它,但外部用户不能调用它。
很可能您制作了 bstnodeiterator::get_pointer()
T*
的 return 类型(而不是 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++参考没有“(或相同)”子句,但添加它是为了与标准保持一致
所以 std::string*
是一个有效的 return 类型,如果该函数覆盖 return 类型 const 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()
指向的字符串,因为它return是一个std::string*
.