开发者

"Proper C++ solution"-- is c-style logic 'bad' when using C++?

开发者 https://www.devze.com 2023-03-30 05:04 出处:网络
So recently I got into a debate about how to solve a problem, the problem specifically was: How do I find all the pallindromes between 1 and 1 million.I said,\"Use atoi to make a string, use a for loo

So recently I got into a debate about how to solve a problem, the problem specifically was: How do I find all the pallindromes between 1 and 1 million. I said, "Use atoi to make a string, use a for loop to reverse the string, the use strcmp to compare the string(s) in question.

A few minutes later someone asked "Why would you use a C-style solution in C++." I found myself confused of a simple, more "C++" way of solving this with code as direct and easy to understand. Anyone care to illuminate me on this one?

edit: itoa开发者_JAVA百科 not atoi


Quite simply, C++ streams are guaranteed to be memory safe and exception safe, failure is distinct from any return value, and C++ strings are memory-safe and exception-safe. C-strings and atoi are hideously unsafe in pretty much every way known to man. Code written in that way is much more error-prone.


Example C++ solution:

#include <algorithm>
#include <iterator>
#include <iostream>
#include <boost/iterator/counting_iterator.hpp>
#include <boost/lexical_cast.hpp>

namespace {
   bool is_palindrome(unsigned int i) {
     const std::string& s = boost::lexical_cast<std::string>(i);
     return std::equal(s.begin(), s.end(), s.rbegin());
   }

   const unsigned int stop = 1000000;
}

int main() {
   std::remove_copy_if(boost::counting_iterator<unsigned int>(1),
                       boost::counting_iterator<unsigned int>(stop),
                       std::ostream_iterator<unsigned int>(std::cout, "\n"),
                       std::not1(std::ptr_fun(is_palindrome)));
}

(I used std::remove_copy_if to make up for the lack of std::copy_if which is in C++0x)

For completeness sake I implemented a version that generates the palindromes rather than testing candidates against a predicate:

#include <boost/lexical_cast.hpp>
#include <boost/iterator/counting_iterator.hpp>
#include <boost/iterator/transform_iterator.hpp>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <cassert>

namespace {
  template <int ver>
  unsigned int make_palindrome(unsigned int i) {
     std::string s = boost::lexical_cast<std::string>(i);
     assert(s.size());
     s.reserve(s.size()*2);
     std::reverse_copy(s.begin(), s.end()-ver, std::back_inserter(s));
     return boost::lexical_cast<unsigned int>(s);
  }
}

int main() {
   typedef boost::counting_iterator<unsigned int> counter;
   std::merge(boost::make_transform_iterator(counter(1), make_palindrome<1>),
              boost::make_transform_iterator(counter(999), make_palindrome<1>),
              boost::make_transform_iterator(counter(1), make_palindrome<0>),
              boost::make_transform_iterator(counter(999), make_palindrome<0>),
              std::ostream_iterator<unsigned int>(std::cout, "\n"));
}

(I could have used boost.range I think instead of std::merge for this)

The discussion point from this I guess then is "is this a better* way to write it?". The thing I like about writing problems like the palindrome in this style is you get the "if it compiles it's probably correct" heuristic on your side. Even if there is a bug it'll still get handled sensibly at run time (e.g. an exception from lexical_cast).

It's a markedly different way of thinking from C programming (but strangely similar to Haskell in some ways). It brings benefits in the form of lots of extra safety, but the compiler error messages can be terrible and shifting the way you think about problems is hard.

At the end of it all though what matters is "is it less work for less bugs?". I can't answer that without some metrics to help.

* For some definition of better.


The C++ equivalent to your solution would be to:

  1. Use stringstream to turn the number into a std::string.
  2. Use std::reverse_copy to reverse the string.
  3. Use == to compare the strings.

1 is better than using itoa (which you probably meant) because you don't have to allocate the memory for the created string yourself and there's no chance of a buffer overrun.

2 is better because again you don't have to worry about allocating memory for the reversed string and you don't duplicate existing functionality.

3 is better because string1 == string2 reads better than using strcmp.


atoi makes it impossible to detect input errors. While a stringstream can do the same job and can errors can be easily detected.


I think a more appropriate question would be why shouldn't you use C-based solutions in C++? If it is simpler and more readable than a "pure" C++ solution, I know I would opt for the C-style solution. Given the solution you came up with was clever and simple, a corresponding C++ solution may be overkill and given the simplicity of the problem I'm not sure what was being suggested that C++ can bring to the solution.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号