C++类包装C结构体

4

我希望写一个C++类来封装一个C语言结构体。以下是一个简单的C语言结构体示例

struct POINT {
  long x;
  long y;
}

我假设以下这个类,但我不确定它是否“高效”,也不确定它是否符合良好的C++风格。我不想使用不必要的变量或函数调用。如果您能改进我的代码,那将非常好:)

这个类的基本思路是它只是结构体的包装/处理程序。这就是为什么setStructgetStruct可以直接修改私有数据并且它只是一个指针。其他成员总是被命名为set<Attribute>get<Attribute>

如果您使用setStruct,唯一的缺点是结构体由于作用域而被删除,因此指针是“无效”的。

namespace wrapper {
class POINT {
  ::POINT * POINT_;

public:
  POINT() {
    POINT_ = new ::POINT;
  }
  ~POINT() {
    delete POINT_;
  }
  inline void setX( long x ) {
    POINT_->x = x;
  }
  inline long getX() {
    return POINT_->x;
  }
  inline void setY( long y ) {
    POINT_->y = y;
  }
  inline long getY() {
    return POINT_->y;
  }
  inline void setStruct(::POINT * __POINT) {
    POINT_ = __POINT;
  }
  inline ::POINT * getStruct() {
    return POINT_;
  }
};
}

1
为什么不只是持有两个LONG成员变量,并定义一个隐式转换运算符到POINT呢?此外,完全没有理由持有一个指向::POINT的指针,而不是::POINT本身。 - Borgleader
2
_POINT是一个保留标识符。此外,在C++14之前,除非你是那1%真正需要使用它的特定场景,否则永远不要使用new,除非在std::make_unique中使用。 - chris
复制一个指针或两个长整型在性能方面差不多。你正在进行微观优化。 - Borgleader
1
就像我说的,你现在就过早地进行优化,从而使你的代码不够明显。用最简单、最明显的方式表达你的想法,如果(且仅当)它被证明是一个瓶颈时才进行优化。(提示:99%的情况下,这不会是问题) - Borgleader
我认为这只是一个更大案例的简化示例。假设它不是“struct POINT”,而是“struct BIGDATA”,其中包含几千兆字节。我认为问题确切地是如何优化,而不是是否需要优化。 - Michael
显示剩余4条评论
2个回答

4
在这种情况下,你最好使用继承而不是组合。这将消除管理额外资源的需要,并允许你的“包装器”作为一个点来操作,而不需要访问器和变异器来处理整个点结构。
namespace wrapper {
    class Point : public ::POINT
    {
    public:
        Point() { }
        ~Point() { }

        // The following accessors/mutators may not be necessary.
        // They can however be handy with code that requires a pointer to
        // member function (i.e. transformations)
        void setX(long nx) { x = nx; }
        long getX() { return x; }
        void setY(long ny) { y = ny; }
        long getY() { return y; }

        // copy assignment operators
        Point& operator=(const POINT& p)
        {
            x = p.x;
            y = p.y;
            return *this;
        }

        Point& operator=(const Point& p)
        {
            x = p.x;
            y = p.y;
            return *this;
        }
    };
}

如果您想防止直接访问POINT的成员,可以使用私有继承。您还可以提供一个转换运算符,以允许从PointPOINT的隐式转换。这将替换POINT* getStruct()成员函数,但仍然允许您轻松地将其与需要POINT作为参数的函数一起使用。

namespace wrapper {
    // Use private inheritance to prevent direct access to the
    // members of POINT
    class Point : private POINT
    {
    public:
        Point() { }
        ~Point() { }

        // Copy constructor
        Point(const ::POINT& p) { x = p.x; y = p.y; }

        // Accessor/mutators
        void setX(long nx) { x = nx; }
        long getX() { return x; }
        void setY(long ny) { y = ny; }
        long getY() { return y; }

        // Allow implicit conversions to POINT* when necessary
        // Replaces getStruct()
        operator ::POINT*() { return this; }
        operator const ::POINT*() const { return this; }

        // Copy assignment operators
        Point& operator=(const POINT& p)
        {
            x = p.x;
            y = p.y;
            return *this;
        }

        Point& operator=(const Point& p)
        {
            x = p.x;
            y = p.y;
            return *this;
        }
    };
}

extern "C" void someCFunction(POINT *);

int main()
{
    POINT cp;
    wrapper::Point p;

    p.x = 0; // FAIL
    p.setX(0); // OK
    p = cp; // OK

    // No need to call getPoint().
    someCFunction(p);
}

注意:我已经移除了使用inline的内容,因为它们是不必要的。在类定义中定义的函数已经是内联的(请参见$7.1.2/3)。感谢Chris提醒我。


你真的需要setter/getter吗?你从一个结构体公开继承,所以我想成员也将是公开的? - Borgleader
我曾考虑过将它们移除,但最终决定不这样做。我添加了一条注释,解释为什么它们“可能”仍然有用。很难说,因为我不知道 OP 还想通过包装器实现什么其他目标。 - Captain Obvlious
这是一个很好的想法。但我会在继承中使用“private”而不是“public”。有反对意见吗? - andrew
@CaptainObvlious:我的目标是创建一个类,提供一种处理C结构的方式,而不使用直接成员调用。无论如何,感谢您提供这个好代码 :) - andrew
1
如果我没记错的话,在类内部使用 inline 标记是多余的。 - chris
显示剩余8条评论

0

如已经注意到的那样,_POINT是一个保留名称,因为它以_ + 大写字母开头。

使用全大写的类型名称是主观的,尽管我倾向于避免这样做。

如果您曾经复制过它或将基于堆栈的 POINT 的地址传递给 setStruct,则您的类将会有各种问题(双重删除、删除非堆内存等)。

如果您只是通过值进行组合,那么您的类将会更加简单,而且出错的可能性也会更小。您可以在交换一些数据的复制级别和间接级别之间进行选择,但很有可能复制会比可能需要两次访问内存的间接级别更好地缓存。

对于显式地转换为和从 C 结构体中获取的 getStructsetStruct 函数并没有任何真正的问题。

然而,我的问题是:您的 C++ 包装器提供了什么,C 结构体没有提供的?您只是提供了包装方法来操作单个属性,而不是某种执行类上其他操作的接口。在这种情况下,我实际上看不到使用包装器的理由(除非您计划添加调试逻辑到 getter 和 setter 中,以便更轻松地跟踪程序流程)。


我把 POINT_ 改成了 _POINT。如何避免这些问题?您能否详细描述并给出一些示例? - andrew
感谢编辑。是的,我想提供更多的功能 :). - andrew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接