假设我有一个跨平台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编译的。可能会延长临时的生命周期,但编译器有错误。
答案 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_