Int który wymusza, żeby był zawsze w odpowiednim zakresie; czemu Segmentation fault?

0

Kombinowałem, jak zrobić własnego inta, który by zawsze wymuszał, że ma być w odpowiednim zakresie (np. od 0 do 100).

Oto, jak się do tego zabrałem:

#include <cassert>
#include <cstdint>
#include <iostream>
using namespace std;

int main();

class ConstrainedInt
{
public:
  typedef uint_least8_t valtype;
  
private:
  valtype val;
  
  static const valtype minval = 0;
  static const valtype maxval = 100;
  static bool checkval (valtype val);

public:
  ConstrainedInt();
  
  ConstrainedInt(valtype);
  ConstrainedInt &operator = (valtype);
  operator const valtype&();
};

bool ConstrainedInt::checkval (ConstrainedInt::valtype val)
{
  return minval <= val && val <= maxval;
}

ConstrainedInt::ConstrainedInt() : val{0}
{
  assert(checkval(val));
}

ConstrainedInt::ConstrainedInt (ConstrainedInt::valtype val) : val{val}
{
  assert(checkval(val));
}

ConstrainedInt &ConstrainedInt::operator = (ConstrainedInt::valtype val)
{
  assert(checkval(val));
  this->val = val;
  return *this;
}

ConstrainedInt::operator const ConstrainedInt::valtype&()
{
  return *this;
}

int main()
{
  ConstrainedInt i = 15;
  cout << i;
  return 0;
}

Dostaję Segmentation fault.

Dlaczego?

0

Dostaję Segmentation fault.

Dlaczego?

Segmentation fault to błąd spowodowany próbą dostania się do pamięci, która do Ciebie nie należy.

Można to zrobić np.

int *p = NULL;
*p = 1;

albo:

char *p= "Foo";
*p = "Bar";

Powinieneś się cieszyć, że dostałeś ten błąd, ponieważ dzięki temu nie masz w programie trudnego do wykrycia błędu pamięci.

0

Abstrahując od błędu - gdy będziesz potrzebował inny zakres napiszesz kolejnà taką klasę?

0

Jejku tyle to ja wiem

Chodzi mi o to, że może jestem ślepy, ale naprawdę nie widzę w moim kodzie operacji, która by odwoływała się do nielegalnego obszaru pamięci

Czy mógłbym prosić o wskazanie mi go?

Według gdb Segmentation fault leci przy return *this; w funkcji ConstrainedInt::operator const ConstrainedInt::valtype&()

Czyli jak rozumiem przy cout << i;

To dziwne przecież i została poprawnie zainicjalizowana i powinna być dostępna dla cout?

@spartanPAGE Nie wiem, na razie dla samokształcenia takie coś piszę ;)

1

Gram teraz, więc gotowcem nie rzucę, ale jeśli chcesz zrobić operator konwersji, to zwróć w nim swoje val

1
ConstrainedInt::operator const ConstrainedInt::valtype&()
{
  return *this;
}

Tutaj powinieneś zwrócić referencję na pole val.

3

Po pierwsze, po co operator konwersji dla valtype const&?

Po drugie, wysil umysł i zastanów się co ten kod oznacza:

ConstrainedInt::operator const ConstrainedInt::valtype&()
{
	return *this;
}

Konkretnie, jakiego typu jest *this?

Tak, jest to ConstrainedInt&, a w jaki sposób zamienić to na ConstrainedInt::valtype&? Ależ oczywiście, można użyć ConstrainedInt::operator const ConstrainedInt::valtype&(). Masz nieskończoną rekurencyjną pętlę wywołań tej samej funkcji, kończy Ci się stos i dostajesz segfaulta.

user image

0

Rrrarrgh jasne dzięki; poprawione, działa

Chyba muszę zrobić sobie przerwę czegoś takiego nie zauważyć

Na marginesie: mamy tu poważny problem, to nie działa z jeszcze jednego powodu

Można napisać ConstrainedInt i = 257; Ponieważ kompilator zawija uint_fast8_t do unsigned char to 257 zawija się do 1 i pasuje do zakresu a intencja była taka żeby wartości powyżej 100 nie dopuszczać

Pojęcia nie mam jak to rozwiązać

Na razie tak to wygląda:

#include <cassert>
#include <cstdint>
#include <iostream>
using namespace std;

int main();

class ConstrainedInt
{
public:
  typedef uint_least8_t valtype;
  
private:
  valtype val;
  
  static const valtype minval;
  static const valtype maxval;
  static bool checkval (valtype val);

public:
  ConstrainedInt();
  
  ConstrainedInt(valtype);
  ConstrainedInt &operator = (valtype);
  operator const valtype&();
};

const ConstrainedInt::valtype ConstrainedInt::minval = 0;

const ConstrainedInt::valtype ConstrainedInt::maxval = 100;

bool ConstrainedInt::checkval (ConstrainedInt::valtype val)
{
  return minval <= val && val <= maxval;
}

ConstrainedInt::ConstrainedInt() : val{0}
{
  assert(checkval(val));
}

ConstrainedInt::ConstrainedInt (ConstrainedInt::valtype val) : val{val}
{
  assert(checkval(val));
}

ConstrainedInt &ConstrainedInt::operator = (ConstrainedInt::valtype val)
{
  assert(checkval(val));
  this->val = val;
  return *this;
}

ConstrainedInt::operator const ConstrainedInt::valtype&()
{
  return val;
}

int main()
{
  ConstrainedInt i(257);
  cout << (int)i;
  return 0;
}

(wypisuje 1)

@kq Niestety głupstwo zrobiłem faktycznie zdecydowane przepraszam za nie

Operator konwersji po to żeby móc wypisywać cout << i

Oczywiście możnaby zamiast tego przeciążyć << i chyba nawet lepiej by było bo ominęlibyśmy problem że cout wypisuje char jako znak nie jako liczbę

Ale wydawało mi się, że operator konwersji będzie ogólniejszy - więcej problemów rozwiąże za jednym zamachem a że zwraca const reference to jest bezpieczny nie grozi przekroczeniem zakresu

2
#include <iostream>
#include <functional>
#include <cassert>
using namespace std;
 
template<
    typename T, 
    T minVal, 
    T maxVal, 
    typename Compare = less<T>>
class RangedVal{
public:
    using value_type = T;
    using compare_type = Compare;
    static constexpr value_type min = minVal;
    static constexpr value_type max = maxVal;
    static constexpr compare_type compare = compare_type();
 
    static constexpr bool check(value_type val){
        return compare(min, val) && compare(val, max);
    }
private:
    value_type value;
public:
    constexpr RangedVal(value_type val): value(check(val)? val : throw val){}

    constexpr operator value_type() const{
        return value;
    }
};

template<class T, T m1, T m2, class Cmp> 
    constexpr typename RangedVal<T, m1, m2, Cmp>::value_type RangedVal<T, m1, m2, Cmp>::min;
template<class T, T m1, T m2, class Cmp> 
    constexpr typename RangedVal<T, m1, m2, Cmp>::value_type RangedVal<T, m1, m2, Cmp>::max;
template<class T, T m1, T m2, class Cmp> 
    constexpr typename RangedVal<T, m1, m2, Cmp>::compare_type RangedVal<T, m1, m2, Cmp>::compare;
 
int main() {
    RangedVal<int, 0, 6> val(3);
    cout << val << endl;
    return 0;
}

//Edit melpon.org/wandbox/permlink/nJvd1wAxsc3iu43z
@Endrju fakt, kompletnie wyleciało mi to z głowy.
@Satirev masz może pomysł dlaczego GCC łyka to bez definicji? (http://ideone.com/Ayd3G1)

0
/tmp/ccG437Qv.o: In function `RangedVal<int, 0, 6, std::less<int> >::check(int)':
test2.cpp:(.text._ZN9RangedValIiLi0ELi6ESt4lessIiEE5checkEi[_ZN9RangedValIiLi0ELi6ESt4lessIiEE5checkEi]+0x13): undefined reference to `RangedVal<int, 0, 6, std::less<int> >::min'
test2.cpp:(.text._ZN9RangedValIiLi0ELi6ESt4lessIiEE5checkEi[_ZN9RangedValIiLi0ELi6ESt4lessIiEE5checkEi]+0x18): undefined reference to `RangedVal<int, 0, 6, std::less<int> >::compare'
test2.cpp:(.text._ZN9RangedValIiLi0ELi6ESt4lessIiEE5checkEi[_ZN9RangedValIiLi0ELi6ESt4lessIiEE5checkEi]+0x2a): undefined reference to `RangedVal<int, 0, 6, std::less<int> >::max'
test2.cpp:(.text._ZN9RangedValIiLi0ELi6ESt4lessIiEE5checkEi[_ZN9RangedValIiLi0ELi6ESt4lessIiEE5checkEi]+0x32): undefined reference to `RangedVal<int, 0, 6, std::less<int> >::compare'
collect2: error: ld returned 1 exit status
2

Dlaczego nie uważam konwersji do const referencji za poprawną:

  1. jest implicit
  2. jest potencjalnie wolniejsza dla użytych typów
  3. nie jest odporna na const_cast
  4. uzależnia czas życia wyniku od czasu życia obiektu głównego

Za dużo lepsze rozwiązanie uważałbym utworzenie funkcji o nazwie typu get_internal_value() czy jakoś tak.

Jeśli chodzi o sprawdzanie zakresów, ja bym użył szablonów albo przyjmował i64/u64 jako wartości wejściowe operatorów.

0

@kq

Ad. 1 Właśnie to chciałem uzyskać... chodziło mi o stworzenie klasy symulującej zachowanie prawdziwego inta, tylko nakładającej ograniczenia
Ad. 2 Tego nie rozumiem, ale OK - wierzę, że tak jest
Ad. 3 Fakt. Ale idąc tym tropem można w ogóle odrzucić stosowanie const referencji. Czy uważasz, że z tego powodu w ogóle należy na ile to tylko możliwe unikać zwracanie gdziekolwiek const referencji? Naprawdę chętnie bym się tego dowiedział, to nie jest pytanie ironiczno - retoryczne.
Ad. 4 Istotnie jest to duża wada, przyznaję. Czy lepszy byłby operator zwracający kopię wartości, a nie referencję?

Chciałem właśnie uniknąć czegoś na kształt get_internal_value właśnie dlatego, że chciałem na ile to możliwe sprawić, by z tej klasy korzystało się analogicznie jak z normalnych intów. Czy takie podejście jest z gruntu złe?

1

Z gruntu złe nie, ale może powodować niespodzianki w przyszłości.

Co do const referencji: mają sens jeśli koszt kopii jest znaczny (co rozumiemy przez znaczny to już można debatować, ale dla nowoczesnych procesorów/kompilatorów lepiej jest skopiować 8- czy 16-bitową wartość, niż synchronizować jej stan i unikać optymalizacji związanych z możliwością użycia const_cast.

2
spartanPAGE napisał(a):
#include <iostream>
#include <functional>
#include <cassert>
using namespace std;
 
template<
    typename T, 
    T minVal, 
    T maxVal, 
    typename Compare = less<T>>
class RangedVal{
public:
    using value_type = T;
    using compare_type = Compare;
    static constexpr value_type min = minVal;
    static constexpr value_type max = maxVal;
    static constexpr compare_type compare = compare_type();
 
    static constexpr bool check(value_type val){
        return compare(min, val) && compare(val, max);
    }
private:
    value_type value;
public:
    constexpr RangedVal(value_type val): value(check(val)? val : throw val){}

    constexpr operator value_type() const{
        return value;
    }
};

template<class T, T m1, T m2, class Cmp> 
    constexpr typename RangedVal<T, m1, m2, Cmp>::value_type RangedVal<T, m1, m2, Cmp>::min;
template<class T, T m1, T m2, class Cmp> 
    constexpr typename RangedVal<T, m1, m2, Cmp>::value_type RangedVal<T, m1, m2, Cmp>::max;
template<class T, T m1, T m2, class Cmp> 
    constexpr typename RangedVal<T, m1, m2, Cmp>::compare_type RangedVal<T, m1, m2, Cmp>::compare;
 
int main() {
    RangedVal<int, 0, 6> val(3);
    cout << val << endl;
    return 0;
}

//Edit melpon.org/wandbox/permlink/nJvd1wAxsc3iu43z
@Endrju fakt, kompletnie wyleciało mi to z głowy.
@Satirev masz może pomysł dlaczego GCC łyka to bez definicji? (http://ideone.com/Ayd3G1)

Przyczyna jest oczywista. Obiekt klasy RangedVal musi być zadeklarowany jako constexpr:

 constexpr RangedVal<int, 0, 6> val(3);

http://melpon.org/wandbox/permlink/AyP8AnZFutsMhCU0

1 użytkowników online, w tym zalogowanych: 0, gości: 1