1

I'm writing some numerical C++ where both std::vector and std::array are being used in performance critical parts of the code. Preferably I would like the operator[] to do range checking in debug mode to weed out any potential out of bounds accesses, but provide no overhead in release build. In principle this could have been easily accomplished with assert statements, but I'm not sure how the stl library code can be edited without doing something bad. I won't consider using the .at() method, since this will give overhead also in a release build. I am using g++ to compile. I have read that asserts can be turned on for the MSVC compiler, but I'm using linux and g++, and I don't think this is an option here.

I have created this derived class for the std::vector, it is working fine for my purposes thus far, but I suspect this is not an ideal solution.

template <typename T> class Vector final : public std::vector<T> 
{ 
public: 
    using std::vector<T>::vector; 
#ifndef NDEBUG 
    T &operator[](size_t i) 
    { 
        assert(i < this->size()); 
        return *(this->_M_impl._M_start + i); 
    } 
    const T &operator[](size_t i) const 
    { 
        assert(i < this->size()); 
        return *(this->_M_impl._M_start + i); 
    } 
#endif 
}; 

I was not able to create a similar derived class for the std::array without problems.This post addressed the issue for the std::vector: Compile time triggered range check for std::vector , but seems like inheriting from the std classes is not popular. One answer from this post also recommends using the flag-D_GLIBCXX_DEBUG, but I found that this check produced errors from another part of the code unrelated to vectors and arrays that was working fine (tried using the yaml-cpp library and accessing a root with the [] operator).

wohlstad
  • 12,661
  • 10
  • 26
  • 39
ander
  • 11
  • 3
  • 2
    One simple option: `foo #if DEBUG .at(42); #else [42]; #endif` ... Would expand to `foo.at(42);` in debug builds and `foo[42];` in release builds. – Jesper Juhl Aug 09 '23 at 00:26
  • MSVC for Windows executable does range checking in debug mode automatically. You might look at what they do in the `vector` source and see if it's easily ported to other OS's. – doug Aug 09 '23 at 01:28
  • 2
    *"`-D_GLIBCXX_DEBUG`, but I found that this check produced errors from another part of the code"* Then that part of the code is bugged, fix it! – HolyBlackCat Aug 09 '23 at 05:48
  • @JesperJuhl I know, but it's too inconvenient to have to write that conditional everytime I access a vector if that's what you mean. – ander Aug 10 '23 at 11:59
  • @doug even if it is an easy solution, how will it help me? Do you reccommend I should edit the vector header? seems like a bad idea. But if that was a solution I could just add an assert to the vector and array operator[] methods – ander Aug 10 '23 at 12:01
  • @HolyBlackCat I haven't had time to look more closely into that part of the code and test, but I almost suspect there is something wrong with the library I'm using - Yaml-cpp. I just used the [] operator for a YAML::Node. I will do a simplified test when I have time, but if it turns out that the error is triggered even if I don't have a bug, I either have do ditch the yaml-cpp library (not really an option) or I can't use `D_GLIBCXX_DEBUG` – ander Aug 10 '23 at 12:06
  • @ander You *could* wrap it in a function so you just do something like `auto foo = access(myvector, some_index);` and then hide the macro ugliness inside the function.. – Jesper Juhl Aug 10 '23 at 12:06
  • @ander If the library is bugged, consider reporting it to them, or sending them a PR with a fix (or making a fork and fixing it there, if they hesitate accepting the PR). `_GLIBCXX_DEBUG` is a powerful tool, and not using it simply because one of your dependencies is slightly bugged doesn't sound like a good idea. – HolyBlackCat Aug 10 '23 at 12:38

1 Answers1

0

You may want:

template <typename T> 
class Vector final : std::vector<T> { ... };

int main() {
    Vector<int> v;

#ifndef NDEBUG
    v[1];    
#else 
    v.at(1);
#endif

    return 0;
}

Or modify the Vector class:

template <typename T> 
class Vector final : std::vector<T> { 
public:
    using std::vector<T>::vector; 
 
    T &operator[](std::size_t i) { 
#ifndef NDEBUG 
        assert(i < this->size()); 
#endif
        return *(this->_M_impl._M_start + i); 
    } 

    const T &operator[](size_t i) const { 
#ifndef NDEBUG 
        assert(i < this->size()); 
#endif
        return *(this->_M_impl._M_start + i); 
    } 
};
  • 1
    The latter solution was basically what I showed in my post, however I was unable to replicate the same solution for the std::array – ander Aug 10 '23 at 11:55
  • @ander Sorry for redundancy. I got what you mean, but it was wrong and if `NDEBUG` would be defined then your `Vector` class wouldn't even have these operators overloaded –  Aug 10 '23 at 13:15