вектор и конст - PullRequest
       74

вектор и конст

23 голосов
/ 20 января 2010

Рассмотрим это

 void f(vector<const T*>& p)
 {
 }
 int main()
 { 
  vector<T*> nonConstVec;
  f(nonConstVec);
 }

Следующее не компилируется. Дело в том, что vector<T*> нельзя преобразовать в vector <const T*>, и это кажется мне нелогичным, поскольку существует неявное преобразование из T* в const T*. Почему это?

vector<const T*> также нельзя преобразовать в vector <T*>, но это ожидается, поскольку const T* не может быть неявно преобразовано в T*.

Ответы [ 10 ]

38 голосов
/ 20 января 2010

Я добавил несколько строк в ваш код. Этого достаточно, чтобы понять, почему это запрещено:

void f(vector<const T*>& p)
 {
    static const T ct;
    p.push_back(&ct); // adds a const T* to nonConstVec !
 }
 int main()
 { 
  vector<T*> nonConstVec;
  f(nonConstVec);
  nonConstVec.back()->nonConstFunction();
 }
22 голосов
/ 20 января 2010

vector<T> и vector<const T> являются несвязанными типами. Тот факт, что T может быть преобразован в const T, здесь ничего не значит.

Вы должны думать об этом с точки зрения системы типов. Instantiated vector<int> абсолютно не похож на vector<const int>.

13 голосов
/ 20 января 2010

Может быть, стоит показать, почему нарушение константности позволяет выполнить преобразование, которое вы хотите:

#include <vector>
const int a = 1;

void addConst(std::vector<const int *> &v) {
    v.push_back(&a); // this is OK, adding a const int* to a vector of same
}

int main() {
    std::vector<int *> w;
    int b = 2;
    w.push_back(&b);  // this is OK, adding an int* to a vector of same
    *(w.back()) = 3;  // this is OK, assigning through an int*
    addConst(w);      // you want this to be OK, but it isn't...
    *(w.back()) = 3;  // ...because it would make this const-unsafe.
}

Проблема в том, что vector<int*>.push_back принимает указатель на неконстантный (который теперь я буду называть "неконстантным указателем"). Это означает, что он может изменить указатель своего параметра. В частности, в случае с вектором, он может передать указатель кому-то еще, кто его модифицирует. Таким образом, вы не можете передать константный указатель на функцию push_back функции w, и желаемое преобразование небезопасно, даже если система шаблонов его поддерживает (а это не так). Цель const-safety - предотвратить передачу константного указателя на функцию, которая принимает неконстантный указатель, и именно так она выполняет свою работу. C ++ требует от вас, чтобы вы конкретно сказали, хотите ли вы сделать что-то небезопасное, поэтому преобразование не может быть неявным. Фактически, из-за того, как работают шаблоны, это вообще невозможно (см. Позже).

Я думаю, что C ++ в принципе может сохранить const-безопасность, разрешив преобразование из vector<T*>& в const vector<const T*>&, так же как int ** в const int *const * безопасно. Но это из-за способа определения вектора: он не обязательно будет безопасным для других шаблонов.

Точно так же теоретически можно разрешить явное преобразование. И фактически, он допускает явное преобразование, но только для объектов, а не ссылок; -)

std::vector<const int*> x(w.begin(), w.end()); // conversion

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

template<typename T> 
struct Foo {
    void Bar(T &);
};

template<>
struct Foo<const int *> {
    void Baz(int *);
};

Теперь Foo<int*> не имеет функции Baz. Как же указатель или ссылка на Foo<int*> могут быть преобразованы в указатель или ссылку на Foo<const int*>?

Foo<int *> f;
Foo<const int *> &g = f; // Not allowed, but suppose it was
int a;
g.Baz(&a); // Um. What happens? Calls Baz on the object f?
6 голосов
/ 20 января 2010

Думайте так:

У вас есть два класса, как это:

class V  { T*       t;};
class VC { T const* t;};

Ожидаете ли вы, что эти два класса будут конвертируемыми автоматически?
В основном это класс шаблона. Каждый вариант совершенно нового типа.

Таким образом, вектор и вектор являются совершенно разными типами.

Мой первый вопрос: вы действительно хотите хранить указатели?

Если да, я бы посоветовал взглянуть на boost :: ptr_container. Он содержит указатели и удаляет их при уничтожении вектора. Но что более важно, он обрабатывает содержащиеся в нем указатели как обычный std: vector обрабатывает содержащиеся в нем объекты. Таким образом, делая вектор const, вы можете получить доступ только к его членам как const

void function(boost::ptr_vector<T> const& x)
{
     x.push_back(new T);  // Fail x is const.
     x[4].plop();         // Will only work if plop() is a const member method.
}

Если вам не нужно хранить указатели, храните объекты (не указатели) в контейнере.

void function(std::vector<T> const& x)
{
     x.push_back(T());    // Fail x is const.
     x[4].plop();         // Will only work if plop() is a const member method.
}
4 голосов
/ 28 октября 2011

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

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

#include <vector>
#include <iostream>

using namespace std;

typedef int T;

T a = 1;
T b = 2;

void f(vector<const T*>& p)
{
    for (vector<const T*>::const_iterator iter = p.begin(); iter != p.end(); ++iter) {
        cout << **iter << endl;
    }
}
vector<const T*>& constify(vector<T*>& v)
{
  // Compiler doesn't know how to automatically convert
  // std::vector<T*> to std::vector<T const*> because the way
  // the template system works means that in theory the two may
  // be specialised differently.  This is an explicit conversion.
  return reinterpret_cast<vector<const T*>&>(v);
}
int main()
{
  vector<T*> nonConstVec;
  nonConstVec.push_back(&a);
  nonConstVec.push_back(&b);
  f(constify(nonConstVec));
}

Я использую reinterpret_cast, чтобы объявить, что две вещи одинаковы. Вы ДОЛЖНЫ чувствовать себя грязным после его использования, но если вы включите его в функцию с комментарием для тех, кто следует за вами, то помойтесь и постарайтесь продолжить с чистой совестью, хотя всегда будет (справедливо) испытывать это ноющее беспокойство по поводу того, что кто-то вырывает землю из-под вас.

3 голосов
/ 20 января 2010

Как уже говорили другие, преобразования не применяются к параметрам шаблона. Другими словами,

vector<T>

... и:

vector<const T>

... это совершенно разные типы.

Если вы пытаетесь реализовать постоянную корректность в отношении того, что f () не изменяет содержимое вектора, это может быть больше похоже на то, что вы ищете:

void f(vector<T>::const_iterator begin, vector<T>::const_iterator end)
{
  for( ; begin != end; ++begin )
  {
    // do something with *begin
  }
}

int main()
{
  vector<T> nonConstVec;
  f(nonConstVec.begin(), nonConstVec.end());
}
1 голос
/ 10 мая 2013

в дополнение к другим ответам, стоит прочитать C ++ FQA Lite, где это (и многие другие функции C ++) обсуждаются из критического POV: http://yosefk.com/c++fqa/const.html#fqa-18.1

0 голосов
/ 20 января 2010

И vector<const T*>, и vector<T*> - совершенно разные типы. Даже если вы напишите const T* внутри вашего main(), ваш код не скомпилируется. Вы должны предоставить специализацию внутри магистрали.

Следующие компиляции:

 #include<vector>
 using namespace std;

 template<typename T>
 void f(vector<const T*>& p)
 {
 }
 int main()
 { 
     vector<const int*> nonConstVec;
     f(nonConstVec);
 }
0 голосов
/ 20 января 2010

Шаблоны немного странные. Тот факт, что существует неявное преобразование из T в U, не означает, что существует неявное преобразование из XXX в XXX. Это может произойти, но для этого нужно немало дополнительной работы в коде шаблона, и я сомневаюсь, что все методы были известны при разработке std::vector (точнее, я довольно уверен, что они не были известны).

Редактировать: Подобные проблемы являются частью мотивации использования итераторов. Даже если container of X неявно преобразуется в container of const X, container<X>::iterator неявно преобразуется в container<X>::const_iterator.

Если вы замените свой:

void f(vector<const T*>& p) {}

с:

template <class const_iter>
void f(const_iter b, const_iter e) {}

Тогда:

int main() { 
    vector<T*> nonConstVec;
    f(nonConstVec.begin(), nonConstVec.end());
    return 0;
}

будет просто отлично - и так будет:

vector<T const *> constVec;
f(constVec.begin(), constVec.end());
0 голосов
/ 20 января 2010

Так работают шаблоны - к параметрам шаблона не применяются преобразования, поэтому два вектора имеют совершенно разные типы.

...