【问题标题】:C++ equivalence to Java's anonymous classC++ 等价于 Java 匿名类
【发布时间】:2020-03-07 01:40:51
【问题描述】:

我正在将一些 Java 代码翻译成 C++。

在 Java 中,我们可以从匿名类创建对象,使用现有的构造函数,并覆盖一些方法。例如,

class X {
    public X(int value) {...}
    public void work() {....}
} 

void main(String[] args) {
    X entity = new X(5) {
         public void work() { /* Something else */ }
    };
}

在 C++ 中,我知道我可以创建如下匿名类:

class X {
public:
    virtual void work() {...}
}

class : public X {
public:
    void work() {....}
} obj;

但是 C++ 不允许匿名类中的构造函数,并且它不允许从对象扩展(例如,new X(5) { public void work() {} } 就像 Java 允许的那样。

如何在 C++ 中编写类似的代码?

更新 03/07/2020 05:27 CDT

关于我正在处理的问题的更多背景信息。我正在实现内存中 SQL 数据库的聚合功能,并使用以下类来表示聚合字段:

class AggField {
public:
    AggField(int colIndex);
    virtual void reduce(DataRow&) = 0;
    virtual double output() = 0;
}

对于每种类型的聚合,例如 avg、min/max 和 sum,我都有一个子类。例如

class Avg : public AggField {
private:
    int counter_;
    double value_;
public:
    Avg(int colIndex) : AggField(colIndex), counter_(0), value_(0) {};
    void reduce(DataRow&) override {
        value_ += row[colIndex].doubleval();
        counter_ += 1;
    }
    double output() override {
        return value_ / counter_;
    }
}

class Sum : public AggField {
    .....
}

在处理一张表的时候,我会写如下

Table table = ...
auto agg_opr = Agg({
    new Sum(0),
    new Avg(1)
});
agg_opr.agg(table);

在第 0 列求和,在第 1 列求平均值。

有时(很少)我需要处理多个输入列。例如,对 col1 * (1 + col2) 求和。我不想创建一个新的 AggField 子类,而是写一些类似的东西:

Table table = ...
auto agg_opr = Agg({
    new Sum(0) {
        void reduce(DataRow& row) {
             value_ += row[0].doubleval() * (1 + row[1].doubleval());
        }
    }, 
    new Avg(1),
    new Max(1)
});
agg_opr.agg(table);

【问题讨论】:

  • 你必须给这个类起个名字。
  • 我想你可以使用一些描述的函数指针来获得类似的效果,需要一个全新的类型。 (如果您无法更改 X 或需要添加到类型,则不是正确的方法)class X { std::function<void()> work = [] () { /***/ }; }; int main() { X foo { [] () { /***/ } }; }
  • 你最好专注于你想要完成的事情。 C++ 的工具与 Java 不同,使用最好的 C++ 工具来完成这项工作可能比尝试在 C++ 中复制 Java 的工具效果更好。

标签: c++ anonymous-class


【解决方案1】:

我不能说我知道如何编写惯用的 Java,但我猜测 Java 中的这种模式是 C++ 中 lambda 的替代方案。我记得很久以前我在使用 Swing 时使用了一个匿名类。我想我做了这样的事情:

button.addMouseListener(new MouseAdapter() {
  public void mouseClicked(MouseEvent e) {
    // ...
  }
});

这是从类继承和覆盖方法的糖。这样做并不是我真正想在 C++ 中附加事件侦听器的方式。我更愿意这样做:

button.addMouseClickListener([](const MouseEvent &e) {
  // ...
});

对于事件监听器,闭包需要存储在std::function 或类似的东西中。这与虚拟呼叫的性能大致相同。

我不太了解您在哪里使用此类,但如果您需要将其存储在一边(例如事件侦听器或其他东西),那么长时间声明该类或使用 std::function 可能是最干净的选项。如果您不需要将其存储在一边(例如算法的策略),那么您可以使用仿函数。当然,您可以将仿函数存储在一边,但它需要一些模板机制,并且可能不值得(尽管它确实具有更大的灵活性)。

struct MyPolicy {
  int doSomething(int i) {
    return i * 3;
  }

  double getSomething() const {
    return d;
  }

  double d;
};

template <typename Policy>
void algorithm(Policy policy) {
  // use policy.doSomething and policy.getSomething...
}

在模板中使用仿函数或 lambda 比使用虚函数具有更好的性能。在上面的示例中,编译器可以并且可能会内联对doSomethinggetSomething 的调用。这对于虚函数是不可能的。

如果我对您要解决的真正问题有更多了解,那么我可能会写出更具体、更有帮助的答案。


看到更新后的问题后,我有另一个建议。那将是为自定义聚合函数创建一个子类。当然,这有很多限制。

template <typename Func>
class CustomAgg : public AggField {
public:
    CustomAgg(int colIndex, Func func)
      : AggField{colIndex}, func{func} {}

    void reduce(DataRow &row) override {
        func(value, row);
    }

    double output() override {
        return value;
    }

private:
    Func func;
    double value = 0.0;
    // could add a `count` member if you want
};

auto agg_opr = Agg({
    new CustomAgg{0, [](double &value, DataRow &row) {
        value += row[0].doubleval() * (1 + row[1].doubleval());
    }},
    new Avg(1),
    new Max(1)
});

老实说,我认为对您来说最好的解决方案是不要尝试在 C++ 中实现 Java 功能。我的意思是,如果您需要在某些特定操作中处理多个列,那么为此创建一个类。不要走任何捷径。给它一个名字,即使你可能只在一个地方使用它。

【讨论】:

  • 感谢您的回答和建议。我写的其实是一个有多种方法的聚合,所以单个函数指针是行不通的。我已经更新了我的问题,以便更准确地了解上下文。
  • 我喜欢使用函数为自定义操作创建单独的子类的想法。
【解决方案2】:

C++ 具有匿名命名空间,它可以有效地让您创建完全独立于声明它们的翻译单元的类:

namespace {

   class X {

   public:
          X(int) { /* ... */ }     // Ok to have a constructor

          void work();
    };

}

int main(int argc, char **argv)
{
    X entity{5};

    // ...
}

现在,你必须在全局范围内声明它们,你不能在内部范围内声明它们。您还需要为这些类提供正常名称,以便在同一个翻译单元中引用它们;但出于所有实际目的,它们是完全匿名的,并且无法从其他翻译单元访问。另一个翻译单元可以声明自己的匿名类“X”,不会有任何冲突。

您可以以所有其他常规方式使用匿名类,对它们进行子类化等...您可以创建一个匿名类,它是常规非匿名类的子类,这让您非常接近 Java 所做的,这里.

一些编译器还提供扩展,您可以在内部范围内声明类,它们的工作方式也与匿名命名空间非常相似,但这将是一个编译器- 特定的扩展。

【讨论】:

    猜你喜欢
    • 2022-01-04
    • 1970-01-01
    • 1970-01-01
    • 2018-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-01
    相关资源
    最近更新 更多