Digit-by-digit addition using vectors I was asked to perform addition by using vectors, so I am wondering if the function I coded looks good. If not, how can I improve the quality of my code? Below, I provided .h and .cpp files. I need feedback on the string constructor and operator+ modules. 

.h file

class ubigint {
   friend ostream&amp; operator&lt;&lt; (ostream&amp;, const ubigint&amp;);
   private:
      /*using unumber = unsigned long;
      unumber uvalue {};
      */
      using udigit_t = unsigned char; 
      using ubigvalue_t = vector&lt;udigit_t&gt;; 
      ubigvalue_t ubig_value; 

   public:
      void multiply_by_2();
      void divide_by_2();

      ubigint() = default; // Need default ctor as well.
      ubigint (unsigned long);
      ubigint (const string&amp;);

      ubigint operator+ (const ubigint&amp;) const;
      ubigint operator- (const ubigint&amp;) const;
      ubigint operator* (const ubigint&amp;) const;
      ubigint operator/ (const ubigint&amp;) const;
      ubigint operator% (const ubigint&amp;) const;

      bool operator== (const ubigint&amp;) const;
      bool operator&lt;  (const ubigint&amp;) const;
};


Currently, I am just done with the constructor and operator+. I feel like my function needs a little improvement since I seem to be repetitive in some places; however, a lot of people may say that the exhaustive creation of modules may lead to poor performance. I just tried to keep my code clean, but I don't know if the quality is good. I neither have checked if the code runs since I have to do other stuff in order to do so.
.cpp file

ubigint::ubigint (const string&amp; that){
   DEBUGF ('~', "that = \"" &lt;&lt; that &lt;&lt; "\"");
   for (char digit: that) {
      if (not isdigit (digit)) {
         throw invalid_argument ("ubigint::ubigint(" + that + ")");
      }
   }

   for(string::reverse_iterator rit = that.str().rbegin(); rit != that.str().rend(); rit++) // iterate the string from end to start and store it in the vector
    {
        ubig_value.push_back(*rit); // push the character
    }  

}



 ubigint ubigint::operator+ (const ubigint&amp; that) const {  //modify to handle vector wise addition

    int carry = 0;  // current carrier
    int counter = 0;  
    int pairAdditionResult = 0;
    int vectorValue = 0;
    udigit_t vectorCharValue = '0';
    ubigvalue_t result;

    do{
        pairAdditionResult = (ubig_value[counter] -'0') + (that[counter] - '0') + carry;
        carry = pairAdditionResult / 10;
        vectorValue = pairAdditionResult % 10;
        vectorCharValue = vectorValue + '0';
        result.push_back(vectorCharValue);
        counter++;

    }while(counter != ubig_value.size() &amp;&amp; counter != that.size())

    if(ubig_value.size() != counter )  // if the size of the vectors differ
    {
        do{
          pairAdditionResult = (ubig_value[counter] - '0') + carry;
          carry = pairAdditionResult / 10;
          vectorValue = pairAdditionResult % 10;
          vectorCharValue = vectorValue  + '0';
          result.push_back(vectorCharValue);
          counter++;

        }while(counter != ubig_value.size())

    }
    else if (that.size() != counter)
    {
        do{
            pairAdditionResult =(that[counter] - '0') + carry;
            carry = pairAdditionResult / 10;
            vectorValue = pairAdditionResult % 10;
            vectorCharValue = vectorValue + '0';
            result.push_back(vectorCharValue);
            counter++;
        }while(counter != that.size(counter != that.size()))

    }

    if(carry != 0)  // for the last digit of the result
    {
        result.push_back(carry + '0');
    }

    int resultVectorSize = result.size();   
    string resultVectorString;  // used for passing the number as a string in the returned object
    while(resultVectorSize != 0)
    {
        resultVectorString.push_back(result[resultVectorSize-1])
        resultVectorSize--;
    }

   return ubigint (resultVectorString);
}
I'm not sure whether you used using namespace std; in the header, or only committed the lesser evil of needlessly importing many names into the global namespace in a header. Anyway, as you are implementing some new library-code, open your own namespace and confine your code to it.
Please read Why is “using namespace std;” considered bad practice?


Let's take a look at your data-structure:


You are using a std::vector, so you have its re-allocation-behavior of roughly doubling the space on demand, and keeping the maximum ever reserved. Not a bad choice.
You store your number as single digit characters. That's workable, but you should really think about storing 0 as 0 not '0'. It makes math easier. Still, I'll keep with that.
Binary operators should, for those where the language allows it, be free functions or at least friend-functions to reduce coupling and enable conversion on the left hand side.
That holds even if you later add overloads to remove those conversions for efficiency.
If you provide some binary operator, it's convenient and customary to also provide the corresponding compound-assignment-operator.


And now your interface:


A conversion (single-argument-ctor or operator Type) should only be implicit if it is lossless and is expected to succeed for all valid inputs.
But "A" is a valid string but will not be accepted.
Use std::string_view instead of constant reference to std::string as input. Thus the caller isn't forced to use a std::string with the possible dynamic allocation (and copy) that entails.


Let's take a look at your ctor:


How does DEBUGF work? Maybe it's a debug-tracing-macro, but what's the first argument for then? Oh well, it's not that important.
You know exactly how long the input is, so why don't you .reserve() space for it to avoid reallocations?
You know calling std::isdigit() with a negative value != EOF is undefined behavior? Cast to unsigned char first!
You optimized your ctor for bad input, as you do a first pass solely to verify the input, and then a second pass to finally store it. Combine the passes into one.
You should probably eliminate leading zeroes. That also makes everything easier.




ubigint::ubigint(std::string_view that) {
    auto pos = that.find_first_not_of('0');
    if(pos != that.npos)
        that = that.substr(pos);

    ubig_value.reserve(that.size());

    for(auto it = rbegin(that); it != rend(that); ++it) {
        if(!isdigit((unsigned char)*it))
            throw invalid_argument ("ubigint::ubigint(" + that + ")");
        ubig_value.push_back((unsigned char)*it);
    }
}


Now, your addition:


While it is often recommended to implement operator+ as a free function in terms of operator+= to avoid repetition, your class doesn't qualify, as it would result in (maybe severe) loss of efficiency. You might want to do it the other way around instead.
Still, that doesn't mean that your try is good. Your first loop repeatedly tests both inputs lengths. Why don't you use std::max(a, b)?
You are using / and % to calculate the carry and the value. While it works, it's highly inefficient. You know the carry is at most one, so use an if-statement and subtraction.
Point 2 comes back to bite you there. See how biasing the numbers with '0' creeps everywhere into the calculations?
The following if-else if-statement is just painful. You are doing the same for both cases, just with a different input-variable. Merge that.
The end really gets me scratching my head. Why do you convert the perfectly usable ubigint into a std::string and then back on returning?
I haven't seen .size() and operator[] in your interface, but adding them is a good idea, so I pretend you already did so. You might want to overhaul your data-structure first though, to be 0 instead of '0'-based.




ubigint operator+ (const ubigint&amp; a, const ubigint&amp; b) {
    if(a.size() &gt; b.size())
        return b + a;

    bool carry = false;
    ubigint result;
    result.reserve(b.size() + 1);
    std::size_t i = 0;
    for(; i &lt; a.size(); ++i) {
        auto c = a[i] + b[i] + carry - '0';
        if(c &lt;= '9') {
            carry = false;
        } else {
            c -= 10;
            carry = true;
        }
        result.push_back(c);
    }

    while(carry &amp;&amp; i &lt; b.size()) {
        auto c = b[i] + carry;
        if(c &lt;= '9')
            carry = false;
        else
            c -= 10;
        result.ubig_value.push_back(c);
    }

    if(!carry)
        std::copy(b.begin() + i, b.end(), std::back_inserter(result));
    else
        result.ubig_value.push_back(c);
    return result;
}