2010-06-30 3 views
7

Вот интересный вопрос о различных причудах языка C++. У меня есть пара функций, которые должны заполнять массив точек углами прямоугольника. Для него есть две перегрузки: один берет Point[5], другой - Point[4]. Пятиточечная версия относится к замкнутому многоугольнику, тогда как 4-точечная версия - это когда вам просто нужно 4 угла, период.C++ для массива меньшего размера

Очевидно, что здесь существует некоторое дублирование работы, поэтому я хотел бы иметь возможность использовать 4-точечную версию для заполнения первых 4 точек версии с 5 точками, поэтому я не дублирую этот код. (Не то, чтобы это было много, чтобы дублировать, но у меня есть ужасные аллергические реакции всякий раз, когда я копирую и вставляю код, и я хотел бы избежать этого.)

Дело в том, что C++, похоже, не заботится о идее преобразование T[m] в T[n], где n < m. static_cast Кажется, что по какой-то причине типы несовместимы. reinterpret_cast отлично справляется, но это опасное животное, которое, как правило, лучше избегать, если это вообще возможно.

Итак, мой вопрос: существует ли безопасный тип литья массива одного размера в массив меньшего размера, где тип массива одинаковый?

[Изменить] Код, да. Я должен был упомянуть, что параметр на самом деле является ссылкой на массив, а не просто на указатель, поэтому компилятор знает разницу типов.

void RectToPointArray(const degRect& rect, degPoint(&points)[4]) 
{ 
    points[0].lat = rect.nw.lat; points[0].lon = rect.nw.lon; 
    points[1].lat = rect.nw.lat; points[1].lon = rect.se.lon; 
    points[2].lat = rect.se.lat; points[2].lon = rect.se.lon; 
    points[3].lat = rect.se.lat; points[3].lon = rect.nw.lon; 
} 
void RectToPointArray(const degRect& rect, degPoint(&points)[5]) 
{ 
    // I would like to use a more type-safe check here if possible: 
    RectToPointArray(rect, reinterpret_cast<degPoint(&)[4]> (points)); 
    points[4].lat = rect.nw.lat; points[4].lon = rect.nw.lon; 
} 

[Edit2] Точка передачи массива по ссылке так что мы можем быть по крайней мере смутно уверены, что абонент проходит в правильном «из параметра».

+0

Пожалуйста, отправьте код, в частности, ваши объявления функций. Функции, объявленные для принятия параметров как массивы, фактически принимают параметры в качестве указателей, поэтому вы не можете перегрузить разницу в размере массива параметра. –

+0

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

ответ

4

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

Я бы сделал это двумя функциями, и пусть они возьмут указатели. Размер не входит в тип указателя

void fillOpenRect(degRect const& rect, degPoint *p) { 
    ... 
} 

void fillClosedRect(degRect const& rect, degPoint *p) { 
    fillOpenRect(rect, p); p[4] = p[0]; 
} 

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


Если вы хотите сделать это в общем, вы можете записать его на выходные итераторы

template<typename OutputIterator> 
OutputIterator fillOpenRect(degRect const& rect, OutputIterator out) { 
    typedef typename iterator_traits<OutputIterator>::value_type value_type; 
    value_type pt[] = { 
    { rect.nw.lat, rect.nw.lon }, 
    { rect.nw.lat, rect.se.lon }, 
    { rect.se.lat, rect.se.lon }, 
    { rect.se.lat, rect.nw.lon } 
    }; 
    for(int i = 0; i < 4; i++) 
    *out++ = pt[i]; 
    return out; 
} 

template<typename OutputIterator> 
OutputIterator fillClosedRect(degRect const& rect, OutputIterator out) { 
    typedef typename iterator_traits<OutputIterator>::value_type value_type; 
    out = fillOpenRect(rect, out); 

    value_type p1 = { rect.nw.lat, rect.nw.lon }; 
    *out++ = p1; 
    return out; 
} 

Вы можете использовать его с векторами, а также с массивами, что вы предпочитаете больше всего.

std::vector<degPoint> points; 
fillClosedRect(someRect, std::back_inserter(points)); 

degPoint points[5]; 
fillClosedRect(someRect, points); 

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

+0

Передавая ссылку на массив, компилятор выполнит проверку типа и проверит правильность размера массива, предоставив ошибку времени компиляции, если ошибочно fillClosedRect вызывается на прямоугольник из 4 точек. Возможно ли, чтобы static_cast использовал массив одного размера для массива другого размера? –

+0

@Stephen нельзя статично произносить такие вещи. Но точка компилятора проверки его спорна. Вы можете передавать только массивы этого определенного размера, и даже если размер проверяется компилятором, который охватывает только проверки типов. Это не гарантирует, что ссылка будет ссылаться на правильный объект массива. Висячие ссылки будут по-прежнему возможны, если вызывающий абонент где-то испортится. Я все для проверки времени компиляции, но в этом случае это, кажется, не имеет никакого преимущества. –

3

Я хотел бы использовать std::vector или (это очень плохо, и не должны использоваться) в некоторых крайних случаях можно даже использовать простые массивы с помощью указателя, как Point* и тогда вы не должны иметь такие «литье» неприятности ,

1

Почему вы не просто пройти стандартный указатель, вместо размера одного, как этот

void RectToPointArray(const degRect& rect, degPoint * points) ; 
+0

Отсутствие типа безопасности. –

+0

Это кажется немного экстремальным, – bobobobo

+0

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

0

Я думаю, вы могли бы использовать шаблон функции специализации, как это (упрощенный пример, где был проигнорирован первый аргумент и имя функции было заменено F() и т.д.):

#include <iostream> 
using namespace std; 

class X 
{ 
}; 

template<int sz, int n> 
int f(X (&x)[sz]) 
{ 
    cout<<"process "<<n<<" entries in a "<<sz<<"-dimensional array"<<endl; 
    int partial_result=f<sz,n-1>(x); 
    cout<<"process last entry..."<<endl; 

    return n; 
} 
//template specialization for sz=5 and n=4 (number of entries to process) 
template<> 
int f<5,4>(X (&x)[5]) 
{ 
    cout<<"process only the first "<<4<<" entries here..."<<endl; 

    return 4; 
} 


int main(void) 
{ 
    X u[5]; 

    int res=f<5,5>(u); 
    return 0; 
} 

конечно, вы должны заботиться о других (потенциально опасных) особых случаях как п = {0,1,2,3} и вам, вероятно, лучше использовать unsigned int ' s вместо ints.

1

Я не думаю, что ваше обрамление/размышление о проблеме верны. Обычно вам не нужно конкретно конкретизировать объект, который имеет 4 вершины против объекта, который имеет 5.

Но если вы ДОЛЖНЫ ввести его, вы можете использовать struct s, чтобы конкретно определить типы вместо этого.

struct Coord 
{ 
    float lat, long ; 
} ; 

Тогда

struct Rectangle 
{ 
    Coord points[ 4 ] ; 
} ; 

struct Pentagon 
{ 
    Coord points[ 5 ] ; 
} ; 

Затем

// 4 pt version 
void RectToPointArray(const degRect& rect, const Rectangle& rectangle) ; 

// 5 pt version 
void RectToPointArray(const degRect& rect, const Pentagon& pent) ; 

Я думаю, что это решение немного экстремальный однако, и std::vector<Coord>, что вы проверить его размер (чтобы быть 4 или 5) как и ожидалось, с assert s, все будет хорошо.

+0

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

0

Так что мой вопрос: существует ли типа безопасный способ литья массив один размер на массив меньшего размера , где тип массива то же самое?

Нет. Я не думаю, что язык позволяет это сделать вообще: рассмотрите вопрос о включении int [10] в int [5]. Однако вы всегда можете получить указатель на него, но мы не можем «обмануть» компилятор, думая, что фиксированный размер имеет другое количество измерений.

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

void RectToPointArray(const degRect& rect, degPoint* points, unsigned int size); 

Если вы установите на работу с массивами, вы можете определить обобщенную функцию, как это:

template <class T, size_t N> 
std::size_t array_size(const T(&/*array*/)[N]) 
{ 
    return N; 
} 

... и использовать это когда вызывая RectToPointArray для передачи аргумента для 'size'. Тогда у вас есть размер, который вы можете определить во время выполнения, и достаточно легко работать с размером - 1 или более подходящим для этого случая. Просто поставьте простой оператор if, чтобы проверить, есть ли 5 ​​элементов или 4.

Позже, если вы передумаете и используете std :: vector, Boost.Array и т. Д., Вы все равно можете использовать эту же старую функцию без ее модификации. Это требует только того, чтобы данные были смежными и изменяемыми. Вы можете получить представление об этом и применить очень общие решения, которые, скажем, требуют только итераторов вперед. Но я не думаю, что эта проблема достаточно сложна, чтобы оправдать такое решение: это было бы похоже на использование пушки, чтобы убить муху; fly swatter в порядке.

Если вы действительно установить на решение у вас есть, то это достаточно легко сделать это:

template <size_t N> 
void RectToPointArray(const degRect& rect, degPoint(&points)[N]) 
{ 
    assert(N >= 4 && "points requires at least 4 elements!"); 
    points[0].lat = rect.nw.lat; points[0].lon = rect.nw.lon; 
    points[1].lat = rect.nw.lat; points[1].lon = rect.se.lon; 
    points[2].lat = rect.se.lat; points[2].lon = rect.se.lon; 
    points[3].lat = rect.se.lat; points[3].lon = rect.nw.lon; 

    if (N >= 5) 
     points[4].lat = rect.nw.lat; points[4].lon = rect.nw.lon; 
} 

Да, есть один ненужный проверка выполнения, но при попытке сделать это во время компиляции, вероятно, аналогично чтобы вытащить вещи из своего перчаточного ящика в попытке повысить топливную экономичность вашего автомобиля. Когда N является выражением постоянной времени компиляции, компилятор, скорее всего, распознает, что условие всегда неверно, когда N < 5 и просто исключить весь этот раздел кода.

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