#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
для людей, которые предпочитают[]
оператор Pythondict
.
Ответ №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.htm2. Вы слишком быстры. Я был в процессе добавления некоторого комментария к этому, с точно такой же ссылкой! Что вы думаете о моем контраргументе, который в основном заключается в том, что мы можем смириться с этим в этом случае, потому что некоторая проверка безопасности лучше, чем ничего?
3. Я согласен, моральный аргумент здесь неприменим. Не уверен, подойдет ли частичная проверка безопасности. Мы можем документально подтвердить, что это применимо только в определенных обстоятельствах, и люди знают, что у них есть дополнительные подводные камни, которых следует избегать с помощью кода в деструкторах. Так что добавление другого может быть не таким уж большим делом, но это и не здорово. Представьте, что если
vector::at()
бы только иногда беспокоились о проверке границ, это имело бы гораздо меньше смысла в качестве интерфейса, чем в настоящее время…