让c++类使用通用的用户定义输入

Make a c++ class work with generic user defined inputs

本文关键字:用户 定义 输入 c++      更新时间:2023-10-16

我觉得这个问题以前一定有人问过,但我在谷歌上找不到答案。如果有,请指引我到一个链接,我会删除这个帖子。

考虑一下这个最小的例子,它代表了我遇到的一个更大的问题。假设我创建了一个简单的"点"answers"打印机"类,如下所示:

class Point {
    public:
        double x, y;
        Point() {x = y = 0;}
        Point(double x, double y) {
            this->x = x; this->y = y;
        }
};
template<typename T>
class Printer {
    public:
        T* mData;
        int mSize;
        // Constructor
        Printer(std::vector<T> &input) {
            mData = &input[0];
            mSize = input.size();
        }
        // Simple Print function
        void Print() {
            printf(" - Showing %d itemsn", mSize);
            for (int i = 0; i < mSize; i++) {
                const T &item = mData[i];
                printf(" - Item %d: (%lf, %lf)n", i, item.x, item.y);
            }
        }
};

我可以像这样使用打印机类:

std::vector<Point> points; // fill the vector, and then...
Printer<Point> pointsPrinter(points); pointsPrinter.Print(); 

现在假设有其他人出现并希望使用Printer类,其中有自己的"Point"类声明如下:

class Pnt {
    public:
        double mX, mY;
        // other stuff
};

如果他们试图这样做:

vector<Pnt> pnts; // Fill the pnts, and then...
Printer<Pnt> pntsPrinter(pnts);
pntsPrinter.Print(); // COMPILE ERROR HERE!

显然,这将失败,因为Pnt没有x或y成员。有没有一种方法可以重写Printer类来处理所有通用用户类型?我不想做的是将Pnt向量复制到Points向量中。

编辑:

我能想到的唯一方法就是传递函数指针。类似这样的东西:

template<typename T>
class Printer {
    public:
        T* mData;
        int mSize;
        double* (*mXFunc) (T*);
        double* (*mYFunc) (T*);
        Printer(std::vector<T> &input, 
                double* (*xFunc) (T*), 
                double* (*yFunc) (T*)) 
        {
            mData = &input[0];
            mSize = input.size();
            mXFunc = xFunc;
            mYFunc = yFunc;
        }
        void Print() {
            printf(" - Showing %d itemsn", mSize);
            for (int i = 0; i < mSize; i++) {
                T &item = mData[i];
                printf(" - Item %d: (%lf, %lf)n", i, *mXFunc(&item), *mYFunc(&item));
            }
        }
};
// Could then use it like so
inline double* getXPointVal(Point *point) {return &point->x;}
inline double* getYPointVal(Point *point) {return &point->y;}
inline double* getXPntVal(Pnt *point) {return &point->mX;}
inline double* getYPntVal(Pnt *point) {return &point->mY;}
Printer<Pnt> pntPrinter(pnts, getXPntVal, getYPntVal);
Printer<Point> pointsPrinter(points, getXPointVal, getYPointVal);
pntPrinter.Print();
pointsPrinter.Print();    

这样做的问题是它看起来很难看,还可能引入函数调用开销。但我想函数调用开销会被编译掉吗?我希望有一个更优雅的解决方案。。。

如果选择cout而不是printf来编写输出,则可以允许所有可打印类型为<<运算符定义重载,并在Printer::print()中通用该重载。过载可能看起来像这样:

std::ostream& operator<<(std::ostream &out, Point& p){
    out << "Point(" << p.x << ", " << p.y << ")";
    return out;
}

附带说明一下,我建议不要存储指向vector的内部存储和大小成员的指针。如果向量需要重新分配,则指针将处于悬空状态且无效。相反,您应该将向量临时作为引用传递,或者保留常量引用。

您可以为要使用的每个Point类定义自由(非成员)函数。这样做的优点是可以稍后定义自由函数,而无需对现有类进行更改。

示例:

namespace A {
   class Point {
   public:
      Point (int x, int y) : x_(x), y_(y) {}
      int getX () const { return x_; }
      int getY () const { return y_; }
   private:
      int x_, y_;
   };
   // in addition, we provide free functions
   int getX (Point const & p) { return p.getX(); }
   int getY (Point const & p) { return p.getY(); }
}
namespace B {
   class Pnt {
   public:
      Pnt (int x, int y) : x_(x), y_(y) {}
      int get_x () const { return x_; }
      int get_y () const { return y_; }
   private:
      int x_, y_;
   };
   // Pnt does not have free functions, and suppose we
   //   do not want to add anything in namespace B
}
namespace PointHelpers {
   // free functions for Pnt
   int getX (Pnt const & p) { return p.get_x (); }
   int getY (Pnt const & p) { return p.get_y (); }
}
// now we can write
template <class PointTy>
void printPoint (PointTy const & p) {
   using PointHelpers::getX;
   using PointHelpers::getY;
   std::cout << getX (p) << "/" << getY (p) << std::endl;
}
A::Point p1 (2,3);
B::Pnt p2 (4,5);
printPoint (p1);
printPoint (p2);

如果自由函数与相应的类位于同一个命名空间中,则将通过依赖于参数的名称查找找到它们。如果您不想在该名称空间中添加任何内容,请创建一个helper名称空间并在其中添加免费函数。然后通过using声明将它们引入作用域。

例如,这种方法类似于STL对beginend所做的操作。

不要指望模板知道给定类/结构的哪些成员对应于您的xy。。。

如果你想创建通用解决方案,你可以告诉你的打印机函数如何使用lambda表达式(c++11解决方案)将给定的对象解释为你的Point类:

#include <iostream>
class Point {
public:
    double x, y;
    Point() {x = y = 0;}
    Point(double x, double y) {
        this->x = x; this->y = y;
    }
};
class Pnt {
public:
    double mX, mY;
    // other stuff
};
template <class P, class L>
void Print(const P &p, L l) {
   Print(l(p));
}
void Print(const Point &p) {
   std::cout << p.x << ", " << p.y << std::endl;
}
int main() {
    Print(Point(1, 2));
    Print(Pnt{4, 5}, [](const Pnt &p) -> Point {return Point(p.mX, p.mY);});
}