毕业生的终身延伸'数据成员和API设计

时间:2016-08-24 16:07:15

标签: c++ api-design temporary data-members

假设我有一个跨平台Path类,如:

class Path {
public:
    // ...
    Path parent() const;                // e.g., /foo/bar -> /foo

    std::string const& as_utf8() const {
        return path;
    }
private:
    std::string path;
};

parent()成员函数返回this路径的父路径,因此它(正确地)返回表示它的新构造的Path对象。

对于将OS级别的路径表示为UTF-8字符串(例如,Unix)的平台,as_utf8()将引用直接返回到内部表示path似乎是合理的,因为它& #39; s 已经 UTF-8。

如果我的代码如下:

std::string const &s = my_path.as_utf8();  // OK (as long as my_path exists)
// ...
Path const &parent = my_path.parent();     // OK (temporary lifetime extended)

这两行都很好,因为:

  • 假设my_path仍然存在,则s仍然有效。
  • parent()返回的临时对象的生命周期由const&扩展。

到目前为止,这么好。但是,如果我的代码如下:

std::string const &s = my_path.parent().as_utf8(); // WRONG

然后这是错误的,因为parent()返回的临时对象的生命周期已延长,因为const&不会 指的是临时的,但是指的是数据成员。此时,如果您尝试使用s,您将获得垃圾或核心转储。如果代码是:

    std::string as_utf8() const {                 // Note: object and NOT const&
        return path;
    }

那么代码就是正确的。但是,每次调用此成员函数时创建临时文件都是低效的。这意味着 no " getter"成员函数应永远返回对其数据成员的引用。

如果API按原样保留,那么调用者似乎会给查看器带来不应有的负担,必须查看as_utf8()的返回类型,看它是否返回const&或不是:如果是,那么调用者必须使用一个对象而一个const&;如果它返回一个对象,那么调用者可以使用const&

那么有没有办法解决这个问题,这样API在大多数情况下都是有效的,但却阻止用户从看似无害的代码中获取悬空引用?

顺便说一句,这是使用g ++ 5.3编译的。可能会延长临时的生命周期,但编译器有错误。

2 个答案:

答案 0 :(得分:7)

你可以做的是创建2个不同版本的int safeTimer = 0; int timeSinceLastLoop; // Add this calculation to your loop private void checkCollision() { safeTimer-= timeSinceLastLoop; if (spBoy.collidesWith(spBall, true) && safeTimer<=0) { this.collides++; safeTimer=3000; // Wait 3 seconds till vulnerability if (this.collides == 3) { //here I will show a Game Over image. } } } ,一个用于左值,一个用于右值。你需要C ++ 11。

通过这种方式,您可以获得两全其美的效果:当对象不是临时对象时为as_utf8(),而当对象不是临时对象时为const&

std::string const& as_utf8() const & {
                               // ^^^ Called from lvalues only
    return path;
}

std::string as_utf8() const && {
                        // ^^^^ Called from rvalues only
    return std::move(path); //We don't need path any more
}

答案 1 :(得分:1)

在我看来,关于是否返回引用或对象的指导原则是检查原始类的定义角色。

即。是暴露一个简单属性的方法(引用参考,特别是如果它是不可变的),还是生成某些东西?

如果它正在生成一个新的对象或表示,我们可以合理地期望它返回一个不同的对象。

API的用户通常习惯于理解属性不会超过其宿主对象。这当然可以在文档中明确说明。

e.g。

private final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture future;

private Random random = new Random();

private int[] getData() {
    int[] result = new int[10];
    for (int i = 0; i < result.length; i++) {
        result[i] = random.nextInt(10);
    }
    return result;
}

private static void dataToSeries(int[] data, XYChart.Series<Number, Number> series) {
    int len = data.length;

    int size = series.getData().size();
    if (size > len) {
        series.getData().subList(len, series.getData().size()).clear();
    } else if (size < len) {
        for (; size < len; size++) {
            series.getData().add(new XYChart.Data<>(0, size));
        }
    }

    for (int i = 0; i < len; i++) {
        series.getData().get(i).setXValue(data[i]);
    }
}

@Override
public void start(Stage primaryStage) {
    ToggleButton btn = new ToggleButton("updating");
    btn.setSelected(false);

    XYChart.Series<Number, Number> series = new XYChart.Series<>();

    LineChart<Number, Number> chart = new LineChart<>(new NumberAxis(), new NumberAxis(), FXCollections.observableArrayList(series));
    chart.setAnimated(false);

    Runnable dataGetter = () -> {
        try {
            // simulate some delay caused by the io operation
            Thread.sleep(100);
        } catch (InterruptedException ex) {
        }
        int[] data = getData();
        Platform.runLater(() -> {
            // update ui
            dataToSeries(data, series);
        });
    };

    btn.selectedProperty().addListener((a, b, newValue) -> {
        if (newValue) {
            // update every second
            future = service.scheduleWithFixedDelay(dataGetter, 0, 1, TimeUnit.SECONDS);
        } else {
            // stop updates
            future.cancel(true);
            future = null;
        }
    });

    VBox root = new VBox(10, chart, btn);

    Scene scene = new Scene(root);

    primaryStage.setScene(scene);
    primaryStage.show();
}

@Override
public void stop() throws Exception {
    service.shutdownNow();
}

在这种情况下,我个人会避免使用struct path { /// a property /// @note lifetime is no longer than the lifetime of this object std::string const& native() const; /// generate a new string representation in a different format std::string to_url() const; }; 的前缀,因为对我而言,这表明我们正在返回同一对象的新表示,例如:

as_