#include<iostream>
#include<set>
template <typename T>
/* Simple smart pointer class */
class SmartPtr
{
    T *ptr;
public:
    explicit SmartPtr(T *p = NULL) { ptr = p; }
    ~SmartPtr() { delete(ptr); }
    T & operator * () { return *ptr; }

    T * operator -> () { return ptr; }

};

class simple {

private:
    int x;
public:
    simple(int y = 0) :x(y) {}
    int getX() { return x; }
};
typedef SmartPtr<simple> simplePtr;



int main() {


    std::set<simplePtr> st;
    simplePtr p1 = simplePtr(new simple(5));
    simplePtr p2 = simplePtr(new simple(5));
    simplePtr p3 = simplePtr(new simple(5));
    simplePtr p4 = simplePtr(new simple(5));

    std::cout << p1->getX();  <-- working fine
    st.insert(p1);
    st.insert(p2);
    st.insert(p3);
    st.insert(p4);

    for (std::set<simplePtr>::iterator it = st.begin(); it != st.end(); ++it)
    {
        std::cout << it->getX();  // Not working??
    }
}


编译失败,并在Visual Studio 2013中出现错误:

Error   C2039   getX: is not a member of SmartPtr<simple>


在Linux上:

error: ‘const class SmartPtr<simple>’ has no member named ‘getX’


这是迭代器的问题吗?

最佳答案

您可以将it->getX()视为(*it).getX()的语法糖。 [原则上,类可以不一致地重载->*(取消引用)运算符,但是std::set<T>::iterator毫不奇怪,不会违反该约定]。因此,在您的情况下,*it被取消引用为const SmartPtr<simple>&类型的左值,并且.getX()失败,因为SmartPtr没有getX()方法。因为,相反,您的意思是访问获得的SmartPtr指向的对象,所以您必须再添加一级解除引用:


  修正1
  
  将it->getX()替换为(**it).getX()(*it)->getX()


但是,还有另一个问题-*it会导致const SmartPtr(是的,std::set的非常数迭代器不提供对容器元素的写访问,否则您可能会破坏正确的顺序)容器中的元素)。但是->中的*SmartPtr(解引用)运算符都以仅在非const对象上被调用的方式定义。要解决此问题,必须使这两个函数const


  校正2(在SmartPtr<T>中)

//                 vvvvv
T & operator * ()  const { return *ptr; }
T * operator -> () const { return ptr; }
//                 ^^^^^



进行第二次更正后,可以用range-for循环替换旧式的for循环:

for (const simplePtr& p : st)
{
    std::cout << p->getX();
}


尽管如此,您的程序仍无法编译-由于无法比较SmartPtr<T>对象,因此无法将它们放入std::set中。通过定义operator<()来解决此问题:


  修正3
  
  添加到SmartPtr<T>

bool operator<(const SmartPtr& other) const { return ptr < other.ptr; }



此时,您的代码可以编译,但是很有可能无法正常工作。原因是SmartPtr<T>的复制语义由编译器自行决定,无法满足您的意图。通过发现违反Rule of Three, Four and Five的方法,很容易猜到-您的类定义了析构函数,但未能定义复制和/或移动构造函数和赋值运算符。结果,您的代码将执行两次删除,因此无法保证任何明确定义的行为。


  校正4
  
  修复SmartPtr<T>的复制语义。


我通过为SmartPtr分配移动语义来“修复”您的代码(这需要在std::move()-将其添加到insert()时添加std::set):

#include<iostream>
#include<set>

template <typename T>
class SmartPtr
{
    T *ptr;
public:
    explicit SmartPtr(T *p = NULL) { ptr = p; }
    ~SmartPtr() { delete(ptr); }
    SmartPtr(const SmartPtr& other) = delete;
    SmartPtr(SmartPtr&& other) : ptr(other.ptr) { other.ptr = NULL; }
    SmartPtr& operator=(SmartPtr other)
    {
        std::swap(ptr, other.ptr);
        return *this;
    }

    T & operator * () const { return *ptr; }
    T * operator -> () const { return ptr; }

    bool operator<(const SmartPtr& other) const { return ptr < other.ptr; }
};

class simple {
    int x;
public:
    simple(int y = 0) : x(y) {}
    int getX() { return x; }
};
typedef SmartPtr<simple> simplePtr;

int main() {
    std::set<simplePtr> st;
    simplePtr p1 = simplePtr(new simple(5));
    simplePtr p2 = simplePtr(new simple(5));

    st.insert(std::move(p1));
    st.insert(std::move(p2));

    for (const simplePtr& p : st)
    {
        std::cout << p->getX();
    }
    return 0;
}

09-13 06:34