Каков наилучший способ указать, что двойное значение не было инициализировано?

#c #null

#c #null

Вопрос:

У меня есть класс CS, который должен представлять систему координат в 3D, т.Е. (x, y, z)

 class CS
{
  private:
        double x;
        double y;
        double z;
}

CS::CS()
{
   x = NULL;//this causes x = 0//i want the address of x to be 0x000000 amp; not x = 0
   y = NULL;
   z = NULL:
}
  

Я хочу, чтобы пользователь мог создать CS (0, 0, 0).
В конструкторе я хочу инициализировать адреса x, y и z равными НУЛЮ.
это делается для того, чтобы различать определяемое пользователем (0, 0, 0) и значение по умолчанию.
Я создаю объекты CS динамически, поэтому нет смысла использовать следующий код:

 class CS
{
  private:
        double *x;
        double *y;
        double *z;
}

CS:CS()
{
    x = new double;
    x = NULL;
    //same for y amp; z
}
  

Прежде всего, я хочу вручную присвоить адрес 0x000000 любой переменной (int или double или char) без использования указателей.
есть предложения?

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

1. Рассматривали ли вы возможность просто добавить флаг is_initialized ? Это было бы простым решением.

2. Почему они в первую очередь являются указателями, почему не просто удваиваются?

3. Вы ищете эквивалент nullable struct ?

4. @JamesMcNellis не могли бы вы показать мне какой-нибудь код … как is_initialized узнает, ввел ли пользователь значение 0 или оно по умолчанию?

5. @MooingDuck: на самом деле, NULL должно быть определено как целочисленный литерал без приведения к void* (или как nullptr в C 11). Однако, исходя из контекста, вопрос касается нулевых указателей и объектов с нулевым значением; разница между ними огромна.

Ответ №1:

Вы не можете изменить позиции x, y и z на NULL, поскольку там позиции всегда будут смещены от объекта CS. Они всегда будут существовать. Это не значит, что CS у x вас есть машина, это CS похоже на x то, что у вас есть голова. У вас не может не быть головы. Если бы они были целыми числами, вам пришлось бы сделать их указателями (как вы сказали, что не хотите этого делать), потому что это был бы единственный способ отличить неинициализированное от инициализированного. Однако у double s есть магическое значение, которое редко используется:

 CS:CS()
: x(std::numeric_limits<double>::quiet_NaN())
: y(std::numeric_limits<double>::quiet_NaN())
: z(std::numeric_limits<double>::quiet_NaN())
{ }
  

Пользователи, вероятно, не будут устанавливать значения x, y и z (НЕ ЧИСЛО) намеренно.

Ответ №2:

Прежде всего, я хочу вручную присвоить адрес 0x000000 любой переменной (int или double или char) без использования указателей. есть предложения?

Это не то, что вы хотите. Что вам нужно, так это возможность определять, была ли установлена переменная или нет.

Другие предлагали такие вещи, как использование определенного значения с плавающей запятой для определения неинициализированного состояния, но я предлагаю использовать Boost .Необязательно. Рассмотрим:

 class CS
{
  private:
    boost::optional<double> x;
    boost::optional<double> y;
    boost::optional<double> z;
}
  

boost::optional либо сохраняет тип, который вы задаете параметру шаблона, либо ничего не сохраняет. Вы можете проверить разницу с помощью простого логического теста:

 if(x)
{
  //Has data
}
else
{
  //Has not been initialized
}
  

Недостатком является то, что доступ к данным немного сложнее:

 x = 5.0; //Initialize the value. x now has data.
y = 4.0 * x; //Fails. x is not a double; it is an optional<double>.
y = 4.0 * (*x); //Compiles, but only works at runtime if x has a value.
  

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

1. привет, большое спасибо. но я также хочу знать, как вручную назначить адрес моим переменным, возможно ли это?

2. @CAD_coding: Нет. Переменные даже не должны иметь адреса, если компилятор этого не хочет.

Ответ №3:

У вас есть несколько вариантов:

  1. Используйте указатели.
  2. Используйте логический флаг рядом с каждой переменной, указывающий, была ли установлена переменная.
  3. Если диапазон допустимых значений ограничен, вы можете использовать специальное значение для обозначения «не установлено». Для double not-a-number часто является естественным кандидатом. Для int и char часто сложнее выбрать хорошее значение.

Ни один из этих вариантов, бесспорно, не лучше двух других, поскольку они предполагают разные компромиссы. Выбирайте сами.

Ответ №4:

Почему вы не можете просто сделать это:

 class CS
{
public:
    // Constructs a CS initialized to 0, 0, 0
    CS() : x(0), y(0), z(0), is_initialized(false) {}

    // User defined values
    CS(double newX, double newY, double newZ) : x(newX), y(newY), z(newZ), is_initialized(true) {}

private:
    double x;
    double y;
    double z;

    // If you need to know that this was initialized a certain way, you could use this suggestion from the comments:
    bool is_initialized;
}
  

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

1. спасибо … я избегал использования дополнительной переменной в классе, чтобы уменьшить использование памяти

Ответ №5:

Если я правильно понимаю, вы хотите иметь возможность определить разницу между недопустимым, сконструированным по умолчанию CS и допустимым CS со значениями (0.0, 0.0, 0.0) . Это именно то, что boost::optional http://www.boost.org/doc/libs/1_47_0/libs/optional/doc/html/index.html это для.

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

1. @CAD_coding: понял только MarkB? Разве ответ Николболаса не является тем же предложением?

Ответ №6:

Вы не можете представить его в том же количестве битов, не имея часового. Если 0 является допустимым числом, вы не можете его использовать. Если вы попытаетесь внедрить обработку null в тип значения, вы получите принципиально неверный и недостижимый код.

При правильной обработке нулей вы ожидаете увидеть такой интерфейс:

 struct foo {
  virtual ~foo() {}
  virtual bool getX(double amp;val) = 0;
  virtual bool getY(double amp;val) = 0;
  virtual bool getZ(double amp;val) = 0;
};
  

Реализация может иметь флаг, который она проверяет перед доступом.

 void some_func(foo *f) {
  double x, y, z;
  if (f->getX(x) amp;amp; f->getY(y) amp;amp; f->getZ(z)) {
    cout << x << ", " << y << ", " << z << endl;
  } else {
    throw std::logic_error("expected some values here");
  }
}
  

Вы не хотите использовать недопустимое значение и не знать его. Очевидно, что проверять возвращаемые значения утомительно, но это дает вам максимальный контроль. У вас также могут быть помощники или перегрузки, которые будут выдавать, если они недействительны.

 struct bar {
  double getX() {
    if (!valid)
      throw std::logic_error("bar is not valid");
    return x;
  }
  bool valid;
  double x, y, z; 
}
  

Для меня разница между foo и bar заключается в том, что низкоуровневый код, обрабатывающий данные, не должен применять политику наличия данных или нет. На более высоких уровнях абстракции вы можете и должны ожидать, должны ли данные быть действительными, когда вы собираетесь их использовать. Оба могут существовать в системе, но foo необходимы.

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

1. Я не вижу, как это решает его проблему. Он хочет выделить массив CS[NUM] и указать, у которого есть набор данных. Как ваша foo помощь?

2. @MooingDuck Неохраняемый доступ к значению, когда вы хотите выразить концепцию null, просто неверен. Предел такого поведения заключается в том, что каждый отдельный разработчик в проекте защищает ваши инварианты с помощью грубой дисциплины. Что хорошего в стратегии, которая в лучшем случае дает вам кошмар обслуживания?

3. Или доступ к значению может быть защищен с помощью an assert , что было бы совершенно нормально для меня. Я только что перечитал ваш интерфейс и понял, что сначала неправильно его понял. Мне не нравится требовать от других проверять возвращаемое значение, но это не стоит -1. Не могли бы вы привести пример использования, чтобы уточнить использование интерфейса, чтобы я мог удалить -1? [

4. Обновлено @MooingDuck. Неважно, throw или assert, пока он быстро завершается сбоем. Я видел, как несколько организаций терпели неудачу при обработке null. Большую часть времени они даже не понимают, в какую ужасную потерю времени они вложили.

Ответ №7:

Одним из способов получить семантику того, что вы хотите, было бы, чтобы тип данных координат был типом, который содержит значение, указывающее, было ли оно присвоено. Что-то вроде этого.

 template<typename T>
class CoordinateValue {
   public:
       CoordinateValue() : uninitialized(true), val(0) {}
       CoordinateValue(T x) : uninitialized(false), val(x) {}
       void setVal(T x) {val = x; uninitialized= false}
       // Trivial getters
    private:
       T val;
       bool uninitialized;
};
  

Я бы предпочел что-то подобное более приятным методам, если только по какой-то причине памяти действительно не хватает.

Если координаты либо все по умолчанию, либо все заданы, тогда у вас может быть один флаг, а не тип данных координат, который включает флаг.

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

1. Я бы сказал, что это самый правильный ответ, который вы можете получить на этот вопрос. В предыдущей работе класс, который мы должны были сделать, был вызван шаблоном Nark<T> . Это позволяет вам проверять, было ли оно присвоено. Частный случай a Nark<bool> также известен как трехбуловое значение.

2. Я бы определенно рекомендовал этот подход, если методы «cuter» не будут работать для того, для чего OP хочет различать значения по умолчанию. (Кроме того, я бы не стал использовать default в качестве имени переменной)

3. @Mooing: если быть точным, вы не можете использовать default в качестве имени переменной, потому что это ключевое слово для switch операторов.

4. Спасибо — я изменю имя. Это научит меня публиковать код, который я не прогонял через компилятор.

5. Также сделал это шаблоном в соответствии с примечанием Майкла Прайса.

Ответ №8:

Я хочу, чтобы пользователь мог создать CS (0, 0, 0). В конструкторе я хочу инициализировать адреса x, y и z равными НУЛЮ. это делается для того, чтобы различать определяемое пользователем (0, 0, 0) и значение по умолчанию. Я создаю объекты CS динамически, поэтому нет смысла использовать следующий код:

В этом проблема. Во-первых, значение по умолчанию? Какое значение по умолчанию? Почему должно быть значение по умолчанию? Это неправильно. И, во-вторых, для вас принципиально невозможно изменить адрес какой-либо переменной.

То, что вы хотите, невозможно выполнить, и даже если бы это было возможно, это было бы ужасно плохой идеей.

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

1. значение по умолчанию присваивается в конструкции… я не могу допустить, чтобы мои переменные имели какое-либо мусорное значение!!!

2. @CAD_coding: затем установите для них значение NULL в конструкторе. Вы не объяснили, почему вам нужно различать фактический NULL и какой-либо другой вид NULL .

3. @CAD_coding: затем установите для них правильное значение или сделайте конструктор по умолчанию незаконным.

4. @CAD_coding: вы полностью можете позволить переменным иметь мусорные значения. Задача вашего пользователя — дать вам что-то значимое для их установки.

Ответ №9:

Вы не можете изменить адрес переменной. И вы не можете присваивать значения указателя (например NULL , или nullptr в C ) переменной не указательного типа, например double .

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

1. По крайней мере, не без некоторого неприглядного приведения.