2016-10-04 2 views
1

У меня есть структура в С, которая представляет собой ряд целых чисел:Структуры и постоянные структуры

typedef struct { 
    int* data; 
    unsigned start; 
    unsigned end; 
} Range; 

У меня есть одна функция, которая записывает значения в диапазоне

bool range_put(Range* const r, int value, unsigned idx) 
{ 
    if(r->start <= idx && idx < r->end) 
    { 
     r->data[idx] = value; 
     return true; 
    } 
    else 
    { 
     return false; 
    } 
} 

и одна функция, которая считывает из a

bool range_get(const Range* const r, int* const out, unsigned idx) 
{ 
    if(r->start <= idx && idx < r->end) 
    { 
     *out = r->data[idx]; 
     return true; 
    } 
    else 
    { 
     return false; 
    } 
} 

Что бы я хотел сделать, это расширить эти функции, чтобы я мог иметь Rang e для константных целых чисел. Что-то вдоль линий

typedef struct { 
    const int* data; 
    unsigned start; 
    unsigned end; 
} ConstRange; 

Как я могу сделать range_get() работать как на Range и ConstRange?

Одним из решений было бы просто дублировать функциональность range_get() на новую функцию bool crange_get(const ConstRange*...), но я хотел бы сохранить количество функций на низком уровне.

Другим решением было бы объединять Range (сомнительное ли это или нет «разрешено»)

typedef union { 
    ConstRange const_range; 
    struct { 
     int* data; 
     unsigned start; 
     unsigned end; 
    } 
} Range; 

Недостатком здесь является то, что синтаксис вызова range_get() становится немного более сложным при переходе в Range

Range range; 
... 
range_get(&range.const_range ...); 

Каковы другие альтернативы, поддерживающие количество функций и сложность в использовании функций вниз?

Range range; 
ConstRange const_range; 
... 
range_get(&range ...); 
range_get(&const_range ...); 
+0

Вы знаете, что, когда у вас есть 'const int *', который указывает на некоторую память, вы не можете изменить или даже * инициализировать * память через свой указатель. Если вы используете, например, 'malloc', чтобы выделить память, тогда вы будете застрять с неинициализированной памятью на всю жизнь. Решение может состоять в том, чтобы отслеживать, какие элементы в диапазоне, которые были записаны на * один раз *, anbd не позволяют им записываться снова. Это может быть выполнено с помощью массива флагов, по одному флагу для каждого элемента в диапазоне.Что касается const/non-const part, достаточно простого булевского флага. –

+1

@JoachimPileborg «... тогда вы не можете изменить ...» - вы ** не должны **. 'const 'является гарантией, предоставленной программистом, не выполняемой компилятором. Это просто позволяет компилятору предупредить пользователя, но не препятствует ему стрелять в ногу. – Olaf

+0

Вы можете использовать «объединение» с двумя значениями. Используйте 'const int *' для всех, кроме функций модификации. – Olaf

ответ

1

У вас нет проблем с большим количеством функций. Единственной (потенциальной) проблемой является дублирование кода. Таким образом, написать не один, а два более функции:

Написать новую функцию bool range_get2(const int* data, unsigned start, unsigned end, const r, int* const out, unsigned idx)

Затем сделайте существующую bool range_get(const Range* const r, int* const out, unsigned idx) работу путем делегирования range_get2(). Обратите внимание, что range_get2() примет либо const int* data, либо int* data; это не против.

Тогда представьте свой новый bool crange_get(const ConstRange*...) и сделайте это также работы делегированием range_get2().

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

1

Как я могу заставить range_get() работать как с Range, так и ConstRange?

TL; DR: вы не можете, по крайней мере, не напрямую.

const -Выбранные типы не являются «совместимыми» с не-const -qualified типами. Таким образом, int и const int несовместимы.Однако для некоторых целей имеет значение, что последняя является качественной версией первой.

Два типа указателей, относящихся к несовместимым типам, несовместимы друг с другом, поэтому int * и const int * несовместимы. Кроме того, в этом случае это одна из const -qualified версия другой.

Типы конструкций с разными тегами или чьи соответствующие имена не имеют совместимых типов, также несовместимы друг с другом, поэтому ваши Range10 и ConstRange несовместимы.

Вы не можете, поэтому, написать соответствующую программу, которая получает доступ к объекту типа ConstRange, как если бы это было типа Range или наоборот. Это запрещено C2011, 6.5/7 («правило строгого сглаживания»). В самом деле, даже не требуется, чтобы представления этих двух типов структуры были выложены соответственно (хотя на практике они почти наверняка будут).

Также вы можете получить доступ к объекту типа int *, как если бы это было типа const int * или наоборот, хотя вы можете конвертировать значение одного из этих типов указателей на другой тип. Поведение такого преобразования хорошо определено в каждом случае, если новый тип не имеет большего требования к выравниванию, чем старый, что было бы очень удивительно здесь. И такое преобразование может быть полезно, поскольку вам разрешен доступ к int как const int - путем разыменования const int *, полученного путем преобразования из int *, например.

Таким образом, вы можете написать соответствующее вспомогательные функции, которые служат в качестве общей задней части для чтения (но не модифицирования) в data членов ConstRange и Range объектов, как это было предложено @MikeNakis, но вы не можете написать соответствующую функцию, которая принимает параметр одного из этих типов и каким-либо образом соответствует соответствующему аргументу другого типа.

Ближе всего вы могли бы использовать союз, но не совсем так, как вы представляете. Использование союза по-прежнему не позволяет получить доступ к Range, как если бы он был ConstRange, и никакая гимнастика вокруг сноски 95 не обходится вокруг того факта, что эти два не должны быть выложены соответственно и что их члены data не соответствует соответствующему типу. На практике такие махинации, скорее всего, приводят к ожидаемому результату, но программа, использующая их, тем не менее не соответствует требованиям и полагается на неопределенное поведение.

Существует, однако, множество подходов, основанных на союзе, которые работают, основываясь на предоставлении способа установить и затем вспомнить, какой член объединения является активным. Рассмотрим:

typedef struct { 
    union { 
     int *data; 
     const int *cdata; 
    }; 
    unsigned start; 
    unsigned end; 
    _Bool is_const; 
} Range; 

_Bool range_get(const Range* const r, int * const out, unsigned idx) { 
    if (r->start <= idx && idx < r->end) { 
     const int *data = r->is_const ? r->cdata : r->data; 

     *out = data[idx]; 
     return true; 
    } else { 
     return false; 
    } 
} 

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

+0

Обратите внимание, что я скопировал ваш стиль, сделав параметры 'r' и' out' '' range_get' '' const', но это не служит никакой полезной цели (в отличие от 'r', также являющегося указатель на объект 'const', который * * служит цели). –

Смежные вопросы