如果对象被用作左值或右值,如何检测和处理

How detect and handle if object is used as l-value or as r-value?

本文关键字:何检测 检测 处理 对象 如果      更新时间:2023-10-16

我有一个叫做Properties的容器类。我想添加operator[](const std::string & name),它将返回具有指定名称的属性。

现在让我们考虑没有指定名称的属性。在这种情况下,我不想添加新的具有指定名称的Property到我的Properties,如果它被用作l值,否则抛出异常。

Properties pts;
pts.add("name1", val1);
pts.add("name2", val2);
pts["name1"] = val3; //OK
pts["name3"] = val1; //OK creating new Property with value = val1
cout << pts["name4"]; //Ooops can't find Property with name = "name4", so throwing an exception

这在c++中可能吗?我怎么写这样的operator[]呢?

您可以涵盖您给出的情况,但不是通过实际检查左值到右值的转换是否发生。我认为直接拦截它是不可能的,所以你需要激发一个不同的转换:

  • operator[]返回代理对象,如John Zwinck所说。

  • 代理对象有一个operator=(const V&),因此通过创建密钥来处理分配。可选地,你也可以有operator+=, operator++和其余的-我不确定你是否意味着任何左值使用是OK的,或者只是直接赋值。

  • 代理对象转换为V&,如果密钥不存在则抛出。

编辑:这似乎是模糊的工作,虽然有用例它不涵盖,如传递operator[]的返回值到一个函数,需要V&和分配给它在那里。此外,隐藏代理+转换永远不会产生与原始类型完全相同的接口,因为隐式转换最多可能涉及一个用户定义的转换,并且代理会"耗尽"该转换。

#include <iostream>
#include <map>
#include <string>
#include <stdexcept>
struct FunnyMap;
struct ProxyValue {
    FunnyMap *ptr;
    std::string key;
    ProxyValue(const std::string &key, FunnyMap *ptr) : ptr(ptr), key(key) {}
    operator int&();
    int &operator=(int i);
};
struct FunnyMap {
    std::map<std::string, int> values;
    ProxyValue operator[](const std::string &key) {
        return ProxyValue(key, this);
    }
};
ProxyValue::operator int&() {
    if (ptr->values.count(key) != 0) {
        return ptr->values[key];
    } else {
        throw std::runtime_error("no key");
    }
}
int &ProxyValue::operator=(int i) {
    return ptr->values[key] = i;
}
void foo(int &i) {
    i = 4;
}
int main() {
    try {
        FunnyMap f;
        f["foo"] = 1;
        std::cout << f["foo"] << "n";
        std::cout << f["bar"];
        // foo(f["bar"]); // also throws
    } catch (const std::exception &e) {
        std::cout << "Exception: " << e.what() << "n";
    }
}
输出:

1
Exception: no key

您可以让operator[]返回一个代理对象,而不是对包含值的普通引用。然后你可以在代理中设置一个标志,当它被分配时(即当代理的操作符=被调用时)。然后,如果从未分配过代理的析构函数,则可以抛出它。当然,你需要用一个布尔值来实例化代理,告诉它是否需要赋值(没有值存在)或不需要赋值(已经设置了值)。

我要指出,抛出析构函数通常被认为是不好的做法。特别是,如果由于另一个异常(例如键查找和值赋值之间的错误)而调用代理的析构函数,则不应该抛出。如果异常已经在运行中,您可能希望跳过从代理的析构函数抛出,您可以使用std::uncaught_exception()检测这种情况。

最后我将引用这篇关于uncaught_exception()的文章:http://www.gotw.ca/gotw/047.htm

有两个参数反对使用这个函数。首先,当抛出实际上是安全的时,它有时可能返回true。我声称我们可以接受你的情况,因为我们正在努力提供安全检查,如果我们有时不能提供安全检查,那么我们的情况也不会比以前差多少。如果我们同意有时不做检查在你的情况下是可以的,那么文章中的第二个参数("道德"的一个)也可以忽略(因为你的代理不会有两个不同的错误处理机制,它会有一个通常是有效的,但并不总是)。