Как определить и обработать, используется ли объект как l-значение или как r-значение?

#c

#c

Вопрос:

У меня есть класс контейнера под названием Properties . Я хочу добавить к нему operator[](const std::string amp; 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[] ?

Комментарии:

1. хороший вопрос. Я знаю, что std::map просто по умолчанию создаст что-то в «name4», и вы не будете знать, что там никогда ничего не вставлялось.

2. ДА. И именно поэтому в std::map нет постоянной версии operator[] . Он всегда добавляет новый элемент со значением по умолчанию, если нет элемента с указанным ключом.

3. ДА. И именно поэтому std::map<>::operator[] следует использовать намного реже, чем есть. Людям это нравится, потому что это «легко», но это также имеет тенденцию маскировать ошибки и приводить к наличию «недопустимых» объектов на карте.

4. @John: Perhpas предполагается, что это вариант std::map для людей, которые предпочитают [] оператор Python dict .

Ответ №1:

Вы можете охватить приведенные вами случаи, но не путем фактической проверки, происходит ли преобразование lvalue в rvalue. Я не думаю, что это возможно напрямую перехватить, поэтому вместо этого вам нужно выполнить другое преобразование:

  • operator[] возвращает прокси-объект, как говорит Джон Цвинк. Простое создание этого прокси-объекта не создает ключ.

  • Прокси-объект имеет operator=(const Vamp;) , так что обрабатывает назначение путем создания ключа. При желании вы также могли бы использовать operator = , operator и остальные — я не уверен, имеете ли вы в виду, что любое использование lvalue допустимо, или просто прямое присвоение.

  • Прокси-объект имеет преобразование в Vamp; , которое выдает, если ключ еще не существует.

Редактировать: это, кажется, работает смутно, хотя есть варианты использования, которые оно не охватывает, такие как передача возвращаемого значения operator[] функции, которая принимает Vamp; и присваивает ему там. Кроме того, скрытие преобразования proxy никогда не приводит к точно эквивалентному интерфейсу, который вы имели бы с исходным типом, потому что неявные преобразования могут включать не более одного пользовательского преобразования, и прокси «использует» это преобразование.

 #include <iostream>
#include <map>
#include <string>
#include <stdexcept>

struct FunnyMap;

struct ProxyValue {
    FunnyMap *ptr;
    std::string key;
    ProxyValue(const std::string amp;key, FunnyMap *ptr) : ptr(ptr), key(key) {}
    operator intamp;();
    int amp;operator=(int i);
};

struct FunnyMap {
    std::map<std::string, int> values;
    ProxyValue operator[](const std::string amp;key) {
        return ProxyValue(key, this);
    }
};

ProxyValue::operator intamp;() {
    if (ptr->values.count(key) != 0) {
        return ptr->values[key];
    } else {
        throw std::runtime_error("no key");
    }
}

int amp;ProxyValue::operator=(int i) {
    return ptr->values[key] = i;
}

void foo(int amp;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 amp;e) {
        std::cout << "Exception: " << e.what() << "n";
    }
}
  

Вывод:

 1
Exception: no key
  

Ответ №2:

Вы могли бы operator[] вернуть прокси-объект, а не простую ссылку на содержащееся значение. Тогда вы могли бы установить флаг в прокси, когда он назначается (т. Е. когда вызывается operator= прокси). Затем вы могли бы запустить деструктор прокси, если он никогда не был назначен. Конечно, вам нужно будет создать экземпляр прокси с логическим значением, сообщающим ему, требовать ли присвоение (значение не существовало) или нет (значение уже было установлено).

Я отмечу, что обычно считается плохой практикой использовать деструктор. В частности, деструктор прокси-сервера не должен вызывать, если он был вызван из-за другого исключения (например, ошибки между поиском ключа и присвоением значения). Вы, вероятно, захотите пропустить выбрасывание из деструктора прокси-сервера, если исключение уже запущено, и вы можете обнаружить это условие с помощью std::uncaught_exception() .

И, наконец, я буду ссылаться на эту статью о uncaught_exception() : http://www.gotw.ca/gotw/047.htm

Это приводит два аргумента против использования этой функции. Во-первых, иногда он может возвращать true, когда его действительно безопасно выбрасывать. Я утверждаю, что мы можем смириться с этим в вашем случае, потому что мы пытаемся обеспечить проверку безопасности, и если мы иногда не в состоянии обеспечить проверку безопасности, то мы не намного хуже, чем раньше. И если мы можем согласиться с тем, что в вашем случае иногда не выполнять проверку нормально, тогда второй аргумент («моральный») в статье также можно игнорировать (поскольку у вашего прокси не будет двух разных механизмов обработки ошибок, у него будет один, который обычно эффективен, но не всегда).

Комментарии:

1. К сожалению, uncaught_exception обнаруживает неправильное условие. Если operator[] вызывается в другом деструкторе , который сам вызывается во время разматывания стека для исключения, то uncaught_exception есть true , но на самом деле этот код, вызванный в деструкторе, может захотеть узнать об ошибке и обработать ее (хотя, конечно, не распространять исключение operator[] за пределы деструктора). Смотрите gotw.ca/gotw/047.htm

2. Вы слишком быстры. Я был в процессе добавления некоторого комментария к этому, с точно такой же ссылкой! Что вы думаете о моем контраргументе, который в основном заключается в том, что мы можем смириться с этим в этом случае, потому что некоторая проверка безопасности лучше, чем ничего?

3. Я согласен, моральный аргумент здесь неприменим. Не уверен, подойдет ли частичная проверка безопасности. Мы можем документально подтвердить, что это применимо только в определенных обстоятельствах, и люди знают, что у них есть дополнительные подводные камни, которых следует избегать с помощью кода в деструкторах. Так что добавление другого может быть не таким уж большим делом, но это и не здорово. Представьте, что если vector::at() бы только иногда беспокоились о проверке границ, это имело бы гораздо меньше смысла в качестве интерфейса, чем в настоящее время…