błąd: przekazanie xxx jako argumentu „ten” xxx odrzuca kwalifikatory

456
#include <iostream>
#include <set>

using namespace std;

class StudentT {

public:
    int id;
    string name;
public:
    StudentT(int _id, string _name) : id(_id), name(_name) {
    }
    int getId() {
        return id;
    }
    string getName() {
        return name;
    }
};

inline bool operator< (StudentT s1, StudentT s2) {
    return  s1.getId() < s2.getId();
}

int main() {

    set<StudentT> st;
    StudentT s1(0, "Tom");
    StudentT s2(1, "Tim");
    st.insert(s1);
    st.insert(s2);
    set<StudentT> :: iterator itr;
    for (itr = st.begin(); itr != st.end(); itr++) {
        cout << itr->getId() << " " << itr->getName() << endl;
    }
    return 0;
}

W linii:

cout << itr->getId() << " " << itr->getName() << endl;

Daje błąd, który:

../main.cpp:35: error: przekazanie „const StudentT” jako argumentu „this” argumentu „int StudentT :: getId ()” odrzuca kwalifikatory

../main.cpp:35: błąd: przekazanie „const StudentT” jako argumentu „ten” argumentu „std :: string StudentT :: getName ()” powoduje odrzucenie kwalifikatorów

Co jest nie tak z tym kodem? Dziękuję Ci!

JASON
źródło
13
Gdzie jest wiersz 35 we fragmencie kodu?
In silico
117
Chciałbym, aby GCC poprawiło ten komunikat o błędzie, np. „Odrzuca kwalifikatory” -> „łamie const poprawność”
jfritz42,
13
@ jfritz42: Byłoby mylące dla sprawy, którą odrzucavolatile
PlasmaHH
3
@PlasmaHH komunikat o błędzie zostanie podzielony na „łamie stałą poprawność” i „łamie zmienność poprawności”. Teraz niewiele osób myśli, że coś jest niestabilne
Caleth

Odpowiedzi:

523

Obiekty w std::setsą przechowywane jako const StudentT. Kiedy więc spróbować zadzwonić getId()z constobiektu kompilator wykryje problem, głównie dzwonisz const const funkcji członka na obiekt, który nie jest dozwolony, ponieważ non-const funkcje składowe sprawiają NO obiecują nie modyfikować obiekt; więc kompilator przyjmie bezpieczne założenie, że getId()może próbować zmodyfikować obiekt, ale jednocześnie zauważy, że obiekt jest const; więc każda próba modyfikacji stałego obiektu powinna być błędem. Dlatego kompilator generuje komunikat o błędzie.

Rozwiązanie jest proste: ustaw funkcje jako:

int getId() const {
    return id;
}
string getName() const {
    return name;
}

Jest to konieczne, ponieważ teraz możesz wywoływać getId()i getName()na stałych obiektach jako:

void f(const StudentT & s)
{
     cout << s.getId();   //now okay, but error with your versions
     cout << s.getName(); //now okay, but error with your versions
}

Jako sidenote powinieneś wdrożyć operator<jako:

inline bool operator< (const StudentT & s1, const StudentT & s2)
{
    return  s1.getId() < s2.getId();
}

Uwaga parametry są teraz constodniesieniem.

Nawaz
źródło
3
Takie jasne wyjaśnienie. Dzięki. Ale zastanawiam się nad twoim ostatnim fragmentem kodu. Po co używać odniesienia w parametrze funkcji? const StudentT & s1, const StudentT & s2?
Rafael Adel
2
@RafaelAdel: Używasz odwołania, aby uniknąć niepotrzebnego kopiowania, a constponieważ funkcja nie musi modyfikować obiektu, więc constwymusza to w czasie kompilacji.
Nawaz
90

Funkcje składowe, które nie modyfikują instancji klasy, należy zadeklarować jako const:

int getId() const {
    return id;
}
string getName() const {
    return name;
}

Ilekroć zobaczysz „odrzuca kwalifikatory”, mówi o constlub volatile.

Fred Larson
źródło
2
@Fred - Czy uważasz, że zdecydowanie konieczne jest dodanie modyfikatorów const do funkcji składowych, które nie modyfikują instancji klasy? Czy w tym przypadku jest jakiś inny powód błędu? Wątpię w to, ponieważ w większości programów pobierających, które piszę, nie dodam do niego modyfikatorów const.
Mahesh
@Mahesh: Tak, to część stałej poprawności . Nie jestem pewien, skąd constpochodzi, ale podejrzewam, że setzwraca stałe odwołanie z iteratora, aby zapobiec zmianie instancji, a tym samym unieważnieniu zestawu.
Fred Larson,
@Mahesh: Nie przejdzie mojej recenzji kodu. Mam współpracownika, który nazywa mnie „konstablem”. 8v) Zmień to foo obj;na const foo obj;raz i zobacz, co się stanie. Lub przekaż constodniesienie do foo.
Fred Larson,
3
@ Mahesh: To tak, jak powiedziałem - jeśli elementy w a setzostaną zmienione, kolejność może zostać zmieniona, a wtedy zestaw nie jest już ważny. W przypadku maptylko klucz jest const. W setprzypadku cały obiekt jest naprawdę kluczem.
Fred Larson
1
@Mahesh: const są niezbędne, w przeciwnym razie nie można wywoływać ich za pomocą obiektów const. zobacz funkcję f()w mojej odpowiedzi.
Nawaz
5

W rzeczywistości standard C ++ (tzn. Wersja robocza C ++ 0x ) mówi (tnx do @Xeo i @Ben Voigt za wskazanie mi tego):

23.2.4 Kontenery asocjacyjne
5 W przypadku zestawu i multisetu typ wartości jest taki sam jak typ klucza. Dla mapy i multimapy jest to para. Klucze w kontenerze asocjacyjnym są niezmienne.
6 iterator kontenera asocjacyjnego należy do kategorii iteratorów dwukierunkowych. W przypadku kontenerów asocjacyjnych, w których typ wartości jest taki sam jak typ klucza, zarówno iterator, jak i const_iterator są stałymi iteratorami. Nie jest określone, czy iterator i const_iterator są tego samego typu.

W związku z tym implementacja Dinkumware VC ++ 2008 jest wadliwa.


Stara odpowiedź:

Wystąpił ten błąd, ponieważ w niektórych implementacjach standardowej biblioteki lib set::iteratorjest taki sam jakset::const_iterator .

Na przykład libstdc ++ (dostarczany z g ++) ma go (zobacz tutaj cały kod źródłowy):

typedef typename _Rep_type::const_iterator            iterator;
typedef typename _Rep_type::const_iterator            const_iterator;

A w dokumentach SGI stwierdza:

iterator       Container  Iterator used to iterate through a set.
const_iterator Container  Const iterator used to iterate through a set. (Iterator and const_iterator are the same type.)

Z drugiej strony VC ++ 2008 Express kompiluje kod bez narzekania, że ​​wywołujesz metody non const na set::iterators.

Eugen Constantin Dinca
źródło
2

Podam bardziej szczegółowy przykład. Co do poniższej struktury:

struct Count{
    uint32_t c;

    Count(uint32_t i=0):c(i){}

    uint32_t getCount(){
        return c;
    }

    uint32_t add(const Count& count){
        uint32_t total = c + count.getCount();
        return total;
    }
};

wprowadź opis zdjęcia tutaj

Jak widać powyżej, IDE (CLion) da wskazówki Non-const function 'getCount' is called on the const object. W metodzie add countjest zadeklarowany jako obiekt const, ale metoda getCountnie jest metodą const, więc count.getCount()może zmienić członków count.

Błąd kompilacji jak poniżej (główny komunikat w moim kompilatorze):

error: passing 'const xy_stl::Count' as 'this' argument discards qualifiers [-fpermissive]

Aby rozwiązać powyższy problem, możesz:

  1. zmień metodę uint32_t getCount(){...}na uint32_t getCount() const {...}. Więc count.getCount()nie zmieni członków count.

lub

  1. zmień uint32_t add(const Count& count){...}na uint32_t add(Count& count){...}. Więc countnie przejmuj się zmianą członków.

Jeśli chodzi o twój problem, obiekty w std :: set są przechowywane jako const StudentT, ale metoda getIdigetName nie są const, więc podajesz powyższy błąd.

Możesz także zobaczyć to pytanie Znaczenie „const” na końcu w deklaracji funkcji klasy? po więcej szczegółów.

Jayhello
źródło