// NOTE: Whilst it is perfectly valid to use Standard C Library, or GNU C Library, routines in // C++ programs, the code examples here will, as far as possible, avoid doing so, instead using // C++-specific functionality and idioms. In general: // * I/O will be iostream-based [i.e. no 'scanf', 'printf', 'fgets' etc] // * Container / iterator idioms based on the Standard Template Library [STL] // will replace the built-in array / raw pointer idioms typically used in C // * Boost Library functionality utilised wherever possible [the reason for // this is that much of this functionality is likely to appear in the next // C++ standard] // * Error detection/handling will generally be exception-based [this is done // to keep examples simple. Exception use is optional in C++, and is not as // pervasive as it is in other languages like Java or C#] // C-based solution(s) to problem(s) will be found in the corresponding section of PLEAC-C/Posix/GNU. #include <iostream> // 'greeted' defined outside of any namespace, class or function, so is part of the // global namespace, and will be visible throughout the entire executable. Should it // be necessary to restrict the visibility of this global identifier to the current // 'compilation unit' [i.e. current source file] then the following may be used: // // namespace { int greeted = 0; } // // The effect is similar to using the 'static' keyword, in this same context, in the C // language. int greeted = 0; int howManyGreetings(); void hello(); // ---- int main() { hello(); int greetings = howManyGreetings(); std::cout << "bye there!, there have been " << greetings << " greetings so far" << std::endl; } // ---- int howManyGreetings() { // Access 'greeted' identifier in the global namespace using the scope resolution // operator. Use of this operator is only necessary if a similarly-named identifier // exists in a return ::greeted; } void hello() { // Here 'greeted' is accessed without additional qualification. Since a 'greeted' identifier // exists only in the global namespace, it is that identifier that is used std::cout << "high there!, this function has been called " << ++greeted << " times" << std::endl; } |
// Standard C++ requires that a function be prototyped, hence the name and type of parameters // must be specified, and the argumemt list in any calls to that function must match the // parameter list, as shown here #include <cmath> double hypotenuse(double side1, double side2); // ---- int main() { double diag = hypotenuse(3.0, 4.0); } // ---- double hypotenuse(double side1, double side2) { return std::sqrt(std::pow(side1, 2.0) + std::pow(side2, 2.0)); } // ---------------------------- // Variable length argument list functions, via the C Language derived 'va_...' macros, // are also supported. However use of this facility is particularly discouraged in C++ // because: // * It is an inherently type-unsafe facility; type safety is a core C++ concern // * Other facilities, such as overloaded functions, and default arguments [neither of which // are available in C] can sometimes obviate the need for variable length argument lists // * OOP techniques can also lessen the need for variable length argument lists. The most // obvious example here is the Iostream library where repeated calls of I/O operators replace // the format string / variable arguments of 'printf' #include <cmath> #include <cstdarg> double hypotenuse(double side1, ...); // ---- int main() { double diag = hypotenuse(3.0, 4.0); } // ---- double hypotenuse(double side1, ...) { // More details available in the corresponding section of PLEAC-C/Posix/GNU va_list ap; va_start(ap, side1); double side2 = va_arg(ap, double); va_end(ap); return std::sqrt(std::pow(side1, 2.0) + std::pow(side2, 2.0)); } // ---------------------------- // An example using default arguments appears below #include <cmath> // Specify default argument values in declaration // Note: This may be done in either of the declaration or the definition [not both], but it // makes more sense to do so in the declaration since these are usually placed in header files // which may be included in several source files. The default argument values would need to be // known in all those locations double hypotenuse(double side1 = 3.0, double side2 = 4.0); // ---- int main() { // All arguments specified double diag = hypotenuse(3.0, 4.0); // Both calls utilise default argument value(s) diag = hypotenuse(3.0); diag = hypotenuse(); } // ---- double hypotenuse(double side1, double side2) { return std::sqrt(std::pow(side1, 2.0) + std::pow(side2, 2.0)); } // ---------------------------- // A [very contrived, not very practical] example using function overloading appears below #include <cmath> double hypotenuse(double side1, double side2); double hypotenuse(double side1); double hypotenuse(); // ---- int main() { // Call version (1) double diag = hypotenuse(3.0, 4.0); // Call version (2) diag = hypotenuse(3.0); // Call version (3) diag = hypotenuse(); } // ---- // (1) double hypotenuse(double side1, double side2) { return std::sqrt(std::pow(side1, 2.0) + std::pow(side2, 2.0)); } // (2) double hypotenuse(double side1) { return std::sqrt(std::pow(side1, 2.0) + std::pow(4.0, 2.0)); } // (3) double hypotenuse() { return std::sqrt(std::pow(3.0, 2.0) + std::pow(4.0, 2.0)); } // ---------------------------- #include <cstddef> #include <vector> std::vector<int> int_all(const double arr[], size_t arrsize); std::vector<int> int_all(const std::vector<double>& arr); // ---- int main() { // Load vectors from built-in arrays, or use Boost 'assign' library const double nums[] = {1.4, 3.5, 6.7}; const size_t arrsize = sizeof(nums) / sizeof(nums[0]); // Conversion effected at vector creation time std::vector<int> ints = int_all(nums, arrsize); // Vector -> vector copy / conversion ints = int_all(std::vector<double>(nums, nums + arrsize)); } // ---- std::vector<int> int_all(const double arr[], size_t arrsize) { return std::vector<int>(arr, arr + arrsize); } std::vector<int> int_all(const std::vector<double>& arr) { std::vector<int> r; r.assign(arr.begin(), arr.end()); // Type safe element copying return r; } // ---------------------------- #include <algorithm> #include <vector> #include <cmath> #include <cstddef> void trunc_em(std::vector<double>& arr); // ---- int main() { // Load vectors from built-in arrays, or use Boost 'assign' library const double nums[] = {1.4, 3.5, 6.7}; const size_t arrsize = sizeof(nums) / sizeof(nums[0]); std::vector<double> numsv(nums, nums + arrsize); trunc_em(numsv); } // ---- void trunc_em(std::vector<double>& arr) { // Replace each element with the value returned by applying 'floor' to that element std::transform(arr.begin(), arr.end(), arr.begin(), floor); } |
// Variables declared within a function body are local to that function, and those declared // outside a function body [and not as part of a class / struct definition, or enclosed within // a namespace] are global, that is, are visible throughout the executable unless their // visibility has been restricted to the source file in which they are defined via enclosing // them within an anonymous namespace [which has the same effect as using the 'static' keyword, // in this same context, in the C language] #include <vector> void somefunc() { // All these variables are local to this function int variable, another; std::vector<int> vec(5); ; // ... } // ---------------------------- // A couple of generic, type-safe type conversion helpers. The Boost Library sports a conversion // library at: http://www.boost.org/libs/conversion/index.html #include <sstream> #include <string> class bad_conversion {}; template<typename T> T fromString(const std::string& s) { std::istringstream iss(s); T t; iss >> t; if (!iss) throw bad_conversion(); return t; } template<typename T> std::string toString(const T& t) { std::ostringstream oss; oss << t << std::ends; if (!oss) throw bad_conversion(); return std::string(oss.str()); } // ------------ #include <string> // File scope variables namespace { std::string name; int age, c, condition; } void run_check(); void check_x(int x); // ---- // An alternative, C++-specific approach, to command-line handling and type conversion // may be seen at: http://www.boost.org/libs/conversion/lexical_cast.htm int main(int argc, char* argv[]) { name.assign(argv[1]); try { age = fromString<int>(argv[2]); } catch (const bad_conversion& e) { ; // ... handle conversion error ... } check_x(age); } // ------------ void run_check() { // Full access to file scope variables condition = 1; // ... } void check_x(int x) { // Full access to file scope variables std::string y("whatever"); run_check(); // 'condition' updated by 'run_check' if (condition) { ; // ... } } |
// Standard C++, owing to its C heritage, allows the creation of 'persistent private variables', // via use of the 'static' keyword. For more details about this, and illustrative code examples, // refer to this same section in PLEAC-C/Posix/GNU. Standard C++-specific methods of perfoming // this task involve use of the 'namespace' facility, or creating a class containing 'static' // members and using access specifiers to restrict access // This example replaces the 'static' keyword with use of an anonymous namespace to force // 'variable' to have file scope, and be visible only within the 'mysubs.cpp file. It is // therefore both persistant [because it is a global variable] and private [because it is // visible only to functions defined within the same source file] // File: 'mysubs.h' void mysub(void); void reset(void); // ---- // File: 'mysubs.cpp' namespace { int variable = 1; } void mysub(void) { ; // ... do something with 'variable' ... } void reset(void) { variable = 1; } // ---- // File: 'test.cpp' #include "mysubs.h" int main() { // 'variable' is not accessable here // Call 'mysub', which can access 'variable' mysub(); // Call 'reset' which sets 'variable' to 1 reset(); } // ------------ // This example is similar to the previous one in using an anonymous namespace to restrict // variable visibility. It goes further, hoewever, grouping logically related items within // a named namespace, thus ensuring access to those items is controlled [i.e. requires // qualification, or a 'using' declaration or directive] // File: 'counter.h' namespace cnt { int increment(); int decrement(); } // ---- // File: 'counter.cpp' namespace cnt { // Ensures 'counter' is visible only within the current source file namespace { int counter = 0; } void reset(int v = 0) { counter = v; } int increment() { return ++counter; } int decrement() { return --counter; } } // ---- // File: 'test.cpp' #include <iostream> #include "counter.h" int main() { // Following line is illegal because 'cnt::counter' is private to the 'counter.cpp' file // int c = cnt::counter; int a = cnt::increment(); std::cout << a << std::endl; a = cnt::decrement(); std::cout << a << std::endl; } // ------------ // This example sees a class containing 'static' members and using access specifiers to // restrict access to those members. Since all the members are static, this class is not // meant to be instantiated [i.e. objects created from it - it can be done, but they would // all be the exact same object :)], but merely uses the 'class' facility to encapsulate // [i.e. group together] and allow selective access [i.e. hide some parts, allow access to // others]. For Design Pattern afficiandos, this is a crude example of the Singleton Pattern // File: 'counter.h' class Counter { public: static int increment(); static int decrement(); private: static int counter; }; // ---- // File: 'counter.cpp' #include "counter.h" int Counter::increment() { return ++counter; } int Counter::decrement() { return --counter; } int Counter::counter = 0; // ---- // File: 'test.cpp' #include <iostream> #include "counter.h" int main() { int a = Counter::increment(); std::cout << a << std::endl; a = Counter::decrement(); std::cout << a << std::endl; } |
// Standard C++ offers no facility for performing adhoc, runtime stack inspection; therefore, // information such as the currently-executing function name, cannot be obtained. Now, this // isn't to say that such facilities don't exist [since, after all, a symbolic debugger works // by doing just this - stack inspection, among other things], but that such features are, for // native code compiled languages like C++, 'extra-language' and development tool-specific |
// Standard C++ supports both // * 'pass-by-value': a copy of an argument is passed when calling a function; in this way // the original is safe from modification, but a copying overhead is incurred which may // adversely affect performance // * 'pass-by-reference': the address of an argument is passed when calling a function; // allows the original to be modified, and incurrs no performance penalty from copying // // The 'pass-by-value' mechanism works in the same way as in the Standard C language [see // corresponding section in PLEAC-C/Posix/GNU]. The 'pass-by-reference' mechanism provides // the same functionality as passing a pointer-to-a-pointer-to-an-argument, but without the // complications arising from having to correctly dereference. Using a reference to a non-const // item allows: // * The item's state to be modified i.e. if an object was passed, it can be mutated [effect // can be mimiced by passing a pointer to the item] // * The item, itself, can be replaced with a new item i.e. the memory location to which the // reference refers is updated [effect can be mimiced by passing a pointer-to-a-pointer to // the item] #include <cstddef> #include <vector> // 'pass-by-value': a copy of each vector is passed as an argument // void array_diff(const std::vector<int> arr1, const std::vector<int> arr2); // 'pass-by-reference': the address of each vector is passed as an argument. Some variants: // * Disallow both vector replacement and alteration of its contents // void array_diff(const std::vector<const int>& arr1, const std::vector<const int>& arr2); // * Disallow vector replacement only // void array_diff(const std::vector<int>& arr1, const std::vector<int>& arr2); // * Disallow alteration of vector contents only // void array_diff(std::vector<const int>& arr1, std::vector<const int>& arr2); // * Allow replacement / alteration // void array_diff(std::vector<int>& arr1, std::vector<int>& arr2); void array_diff(const std::vector<int>& arr1, const std::vector<int>& arr2); // ---- int main() { // Load vectors from built-in arrays, or use Boost 'assign' library const int arr1[] = {1, 2, 3}, arr2[] = {4, 5, 6}; const size_t arrsize = 3; // Function call is the same whether 'array_diff' is declared to be 'pass-by-value' // or 'pass-by-reference' array_diff(std::vector<int>(arr1, arr1 + arrsize), std::vector<int>(arr2, arr2 + arrsize)); } // ---- // void array_diff(const std::vector<int> arr1, const std::vector<int> arr2) // { // ; // 'arr1' and 'arr2' are copies of the originals // } void array_diff(const std::vector<int>& arr1, const std::vector<int>& arr2) { ; // 'arr1' and 'arr2' are references to the originals } // ---------------------------- #include <cstddef> #include <algorithm> #include <functional> #include <vector> std::vector<int> add_vecpair(const std::vector<int>& arr1, const std::vector<int>& arr2); // ---- int main() { // Load vectors from built-in arrays, or use Boost 'assign' library const int aa[] = {1, 2}, ba[] = {5, 8}; size_t arrsize = 2; const std::vector<int> a(aa, aa + arrsize), b(ba, ba + arrsize); std::vector<int> c = add_vecpair(a, b); } // ---- std::vector<int> add_vecpair(const std::vector<int>& arr1, const std::vector<int>& arr2) { std::vector<int> retvec; retvec.reserve(arr1.size()); std::transform(arr1.begin(), arr1.end(), arr2.begin(), back_inserter(retvec), std::plus<int>()); return retvec; } |
// Please refer to the corresponding section in PLEAC-C/Posix/GNU since the points raised there // apply to C++ also. Examples here don't so much illustrate C++'s handling of 'return context' // as much as how disparate types might be handled in a reasonably uniform manner // Here, 'mysub' is implemented as a function template, and its return type varies with the // argument type. In most cases the compiler is able to infer the return type from the // argument, however, it is possible to pass the type as a template parameter. Note this // code operates at compile-time, as does any template-only code #include <cstddef> #include <string> #include <vector> template <typename T> T mysub(const T& t) { return t; } // ---- int main() { // 1. Type information inferred by compiler int i = mysub(5); double d = mysub(7.6); const int arr[] = {1, 2, 3}; const size_t arrsize = sizeof(arr) / sizeof(arr[0]); std::vector<int> v = mysub(std::vector<int>(arr, arr + arrsize)); // 2. Type information provided by user // Pass a 'const char*' argument and specify type information in the call std::string s = mysub<std::string>("xyz"); // Could avoid specifying type information by passing a 'std::string' argument // std::string s = mysub(std::string("xyz")); } // ---------------------------- // This is a variant on the previous example that uses the Boost Library's 'any' type as a // generic 'stub' type #include <string> #include <vector> #include <boost/any.hpp> template <typename T> boost::any mysub(const T& t) { return boost::any(t); } // ---- int main() { std::vector<boost::any> any; // Add various types [encapsulated in 'any' objects] to the container any.push_back(mysub(5)); any.push_back(mysub(7.6)); any.push_back(mysub(std::vector<int>(5, 5))); any.push_back(mysub(std::string("xyz"))); // Extract the various types from the container by appropriately casting the relevant // 'any' object int i = boost::any_cast<int>(any[0]); double d = boost::any_cast<double>(any[1]); std::vector<int> v = boost::any_cast< std::vector<int> >(any[2]); std::string s = boost::any_cast<std::string>(any[3]); } |
// Just like the C language, C++ offers no support for named / keyword parameters. It is of // course possible to mimic such functionality the same way it is done in C [see corresponding // section in PLEAC-C/Posix/GNU], the most obvious means being by passing a set of key/value // pairs in a std::map. This will not be shown here. Instead, two quite C++-specific examples // will be provided, based on: // // * Named Parameter Idiom [see: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.18] // * Boost 'parameter' Library [see: http://www.boost.org/libs/parameter/doc/html/index.html] #include <iostream> #include <map> class TimeEntry { public: explicit TimeEntry(int value = 0, char dim = 's'); bool operator<(const TimeEntry& right) const; friend std::ostream& operator<<(std::ostream& out, const TimeEntry& t); private: int value_; char dim_; }; typedef std::pair<const int, TimeEntry> TENTRY; typedef std::map<const int, TimeEntry> TIMETBL; class RaceTime { public: const static int START_TIME, FINISH_TIME, INCR_TIME; public: explicit RaceTime(); RaceTime& start_time(const TimeEntry& time); RaceTime& finish_time(const TimeEntry& time); RaceTime& incr_time(const TimeEntry& time); friend std::ostream& operator<<(std::ostream& out, const RaceTime& r); private: TIMETBL timetbl_; }; const int RaceTime::START_TIME = 0, RaceTime::FINISH_TIME = 1, RaceTime::INCR_TIME = 2; void the_func(const RaceTime& r); // ---- int main() { the_func(RaceTime().start_time(TimeEntry(20, 's')).finish_time(TimeEntry(5, 'm')).incr_time(TimeEntry(5, 's'))); the_func(RaceTime().start_time(TimeEntry(5, 'm')).finish_time(TimeEntry(30, 'm'))); the_func(RaceTime().start_time(TimeEntry(30, 'm'))); } // ---- std::ostream& operator<<(std::ostream& out, const TimeEntry& t) { out << t.value_ << t.dim_; return out; } std::ostream& operator<<(std::ostream& out, const RaceTime& r) { RaceTime& r_ = const_cast<RaceTime&>(r); out << "start_time: " << r_.timetbl_[RaceTime::START_TIME] << "\nfinish_time: " << r_.timetbl_[RaceTime::FINISH_TIME] << "\nincr_time: " << r_.timetbl_[RaceTime::INCR_TIME]; return out; } TimeEntry::TimeEntry(int value, char dim) : value_(value), dim_(dim) {} bool TimeEntry::operator<(const TimeEntry& right) const { return (dim_ == right.dim_) ? (value_ < right.value_) : !(dim_ < right.dim_); } RaceTime::RaceTime() { timetbl_.insert(TENTRY(START_TIME, TimeEntry(0, 's'))); timetbl_.insert(TENTRY(FINISH_TIME, TimeEntry(0, 's'))); timetbl_.insert(TENTRY(INCR_TIME, TimeEntry(0, 's'))); } RaceTime& RaceTime::start_time(const TimeEntry& time) { timetbl_[START_TIME] = time; return *this; } RaceTime& RaceTime::finish_time(const TimeEntry& time) { timetbl_[FINISH_TIME] = time; return *this; } RaceTime& RaceTime::incr_time(const TimeEntry& time) { timetbl_[INCR_TIME] = time; return *this; } void the_func(const RaceTime& r) { std::cout << r << std::endl; } // ---------------------------- // The Boost 'parameter' library requires a significant amount of setup code to be written, // much more than this section warrants. My recommendation is to read carefully through the // tutorial to determine whether a problem for which it is being considered justifies all // the setup. |
// The Boost 'tuple' Library also allows multiple assignment to variables, including the // selective skipping of return values #include <iostream> #include <boost/tuple/tuple.hpp> typedef boost::tuple<int, int, int> T3; T3 func(); // ---- int main() { int a = 6, b = 7, c = 8; std::cout << a << ',' << b << ',' << c << std::endl; // A tuple of references to the referred variables is created; the values // captured from the returned tuple are thus multiply-assigned to them boost::tie(a, b, c) = func(); std::cout << a << ',' << b << ',' << c << std::endl; // Variables can still be individually referenced a = 11; b = 23; c = 56; std::cout << a << ',' << b << ',' << c << std::endl; // Return values may be ignored; affected variables retain existing values boost::tie(a, boost::tuples::ignore, c) = func(); std::cout << a << ',' << b << ',' << c << std::endl; } // ---- T3 func() { return T3(3, 6, 9); } |
// Like Standard C, C++ allows only the return of a single value. The return of multiple values // *can*, however, be simulated by packaging them within an aggregate type [as in C], or a // custom class, or one of the STL containers like std::vector. Probably the most robust, and // [pseudo]-standardised, approach is to use the Boost 'tuple' Library, as will be done in this // section. Notes: // * Use made of Boost 'assign' Library to simplify container loading; this is a *very* handy // library // * Use made of Boost 'any' Library to make containers heterogenous; 'variant' Library is // similar, and is more appropriate where type-safe container traversal is envisaged e.g. // for printing #include <string> #include <vector> #include <map> #include <boost/any.hpp> #include <boost/tuple/tuple.hpp> #include <boost/assign/std/vector.hpp> #include <boost/assign/list_inserter.hpp> typedef std::vector<boost::any> ARRAY; typedef std::map<std::string, boost::any> HASH; typedef boost::tuple<ARRAY, HASH> ARRAY_HASH; ARRAY_HASH some_func(const ARRAY& array, const HASH& hash); // ---- int main() { // Load containers using Boost 'assign' Library using namespace boost::assign; ARRAY array; array += 1, 2, 3, 4, 5; HASH hash; insert(hash) ("k1", 1) ("k2", 2) ("k3", 3); // Pass arguments to 'somefunc' and retrieve them as members of a tuple ARRAY_HASH refs = some_func(array, hash); // Retrieve copy of 'array' from tuple ARRAY ret_array = boost::get<0>(refs); // Retrieve copy of 'hash' from tuple HASH ret_hash = boost::get<1>(refs); } // ---- ARRAY_HASH some_func(const ARRAY& array, const HASH& hash) { ; // ... do something with 'array' and 'hash' return ARRAY_HASH(array, hash); } |
// Like function calls in Standard C, function calls in C++ need to conform to signature // requirements; a function call must match its declaration with the same number, and type, // of arguments passed [includes implicitly-passed default arguments], and the same return // value type. Thus, unlike Perl, a function declared to return a value *must* do so, thus // cannot 'return nothing' to indicate failure. // Whilst in Standard C certain conventions like returning NULL pointers, or returning -1, to // indicate the 'failure' of a task [i.e. function return codes are checked, and control // proceeds conditionally] are used, Standard C++ sports facilities which lessen the need for // dong the same. Specifically, C++ offers: // * Built-in exception handling which can be used to detect [and perhaps recover from], // all manner of unusual, or erroneous / problematic situations. One recommended use is // to avoid writing code that performs a lot of return code checking // * Native OOP support allows use of the Null Object Design Pattern. Put simply, rather than // than checking return codes then deciding on an action, an object with some predefined // default behaviour is returned / used where an unusual / erroneous / problematic situation // is encountered. This approach could be as simple as having some sort of default base // class member function behaviour, or as complex as having a diagnostic-laden object created // * Functions can still return 'error-indicating entities', but rather than primitive types // like 'int's or NULL pointers, complex objects can be returned. For example, the Boost // Library sports a number of such types: // - 'tuple' // - 'any', 'variant' and 'optional' // - 'tribool' [true, false, indeterminate] // Exception Handling Example class XYZ_exception {}; int func(); // ---- int main() { int valid_value = 0; try { ; // ... valid_value = func(); ; // ... } catch(const XYZ_exception& e) { ; // ... } } // ---- int func() { bool error_detected = false; int valid_value; ; // ... if (error_detected) throw XYZ_exception(); ; // ... return valid_value; } // ------------ // Null Object Design Pattern Example #include <iostream> class Value { public: virtual void do_something() = 0; }; class NullValue : public Value { public: virtual void do_something(); }; class ValidValue : public Value { public: virtual void do_something(); }; Value* func(); // ---- int main() { // Error checking is performed within 'func'. However, regardless of the outcome, an // object of 'Value' type is returned which possesses similar behaviour, though appropriate // to whether processing was successful or not. In this way no error checking is needed // outside of 'func' Value* v = func(); v->do_something(); delete v; } // ---- void NullValue::do_something() { std::cout << "*null*" << std::endl; } void ValidValue::do_something() { std::cout << "valid" << std::endl; } Value* func() { bool error_detected = true; ; // ... if (error_detected) return new NullValue; ; // ... return new ValidValue; } // ---------------------------- // The Boost 'optional' library has many uses, but in the current context, one is of particular // use: returning a specified type [thus satisfying language requirements], but whose value // may be 'set' [if the function succeeded] or 'unset' [if it failed], and this condition very // easily checked #include <iostream> #include <cstdlib> #include <string> #include <vector> #include <map> #include <boost/optional/optional.hpp> class func_fail { public: explicit func_fail(const std::string& msg) : msg_(msg) {} const std::string& msg() const { return msg_; } private: const std::string msg_; }; // ---- void die(const std::string& msg); boost::optional<int> sfunc(); boost::optional< std::vector<int> > afunc(); boost::optional< std::map<std::string, int> > hfunc(); // ------------ int main() { try { boost::optional<int> s; boost::optional< std::vector<int> > a; boost::optional< std::map<std::string, int> > h; if (!(s = sfunc())) throw func_fail("'sfunc' failed"); if (!(a = afunc())) throw func_fail("'afunc' failed"); if (!(h = hfunc())) throw func_fail("'hfunc' failed"); ; // ... do stuff with 's', 'a', and 'h' ... int scalar = *s; ; // ... } catch (const func_fail& e) { die(e.msg()); } ; // ... other code executed if no error above ... } // ------------ void die(const std::string& msg) { std::cerr << msg << std::endl; // Should only be used if all objects in the originating local scope have been destroyed std::exit(EXIT_FAILURE); } // ---- boost::optional<int> sfunc() { bool error_detected = true; int valid_int_value; ; // ... if (error_detected) return boost::optional<int>(); ; // ... return boost::optional<int>(valid_int_value); } boost::optional< std::vector<int> > afunc() { // ... code not shown ... return boost::optional< std::vector<int> >(); // ... code not shown } boost::optional< std::map<std::string, int> > hfunc() { // ... code not shown ... return boost::optional< std::map<std::string, int> >(); // ... code not shown ... } |
// Whilst in Perl function prototyping is optional, this is not the case in C++, where it is // necessary to: // * Declare a function before use; this could either be a function declaration separate from // the function definition, or the function definition itself which serves as its own // declaration // * Specify both parameter positional and type information; parameter names are optional in // declarations, mandatory in definitions // * Specify return type #include <iostream> #include <vector> // Function Declaration std::vector<int> myfunc(int arg1, int arg2); // Also possible: std::vector<int> myfunc(int, int); // ---- int main() { // Call function with all required arguments; this is the only calling method // [except for calling via function pointer which still needs all arguments supplied] std::vector<int> results = myfunc(3, 5); // Let's look at our return array's contents std::cout << results[0] << ':' << results[1] << std::endl; } // ---- // Function Definition std::vector<int> myfunc(int arg1, int arg2) { std::vector<int> r; std::back_inserter(r) = arg1; std::back_inserter(r) = arg2; return r; } // ------------ // A version on the above code that is generic, that is, making use of the C++ template // mechanism to work with any type #include <iostream> #include <vector> // Function Declaration template <class T> std::vector<T> myfunc(const T& arg1, const T& arg2); // ---- int main() { std::vector<int> results = myfunc(3, 5); std::cout << results[0] << ':' << results[1] << std::endl; } // ---- // Function Definition template <class T> std::vector<T> myfunc(const T& arg1, const T& arg2) { std::vector<T> r; std::back_inserter(r) = arg1; std::back_inserter(r) = arg2; return r; } // ------------ // Other Perl examples are omitted since there is no variation in C++ function calling or // parameter handling |
// One of the key, non-object oriented features of Standard C++ is its built-in support for // exceptions / exception handling. The feature is well-integrated into the language, including // a set of predefined exception classes included in, and used by, the Standard Library, is // quite easy to use, and helps the programmer write robust code provided certain conventions // are followed. On the downside, the C++ exception handling system is criticised for imposing // significant runtime overhead, as well as increasing executable code size [though this // varies considerably between CPU's, OS's, and compilers]. Please refer to the corresponding // section in PLEAC-C/Posix/GNU for pertinent reading references. // // The example code below matches the PLEAC-C/Posix/GNU example rather than the Perl code. Note: // * A very minimal, custom exception class is implemented; a more complex class, one richer in // diagnostic information, could have been implemented, or perhaps one based on a standard // exception class like 'std::exception' // * Ordinarily error / exception messages are directed to 'std::cerr' or 'std::clog' // * General recommendation is to throw 'temporaries' [via invoking a constructor], // and to 'catch' as const reference(s) // * Proper 'cleanup' is very important; consult a suitable book for guidance on writing // 'exception safe' code #include <iostream> #include <string> class FullmoonException { public: explicit FullmoonException(const std::string& msg) : msg_(msg) {} friend std::ostream& operator<<(std::ostream& out, const FullmoonException& e) { out << e.msg_; return out; } private: const std::string msg_; }; // ---- int main() { std::cout << "main - entry" << std::endl; try { std::cout << "try block - entry" << std::endl; std::cout << "... doing stuff ..." << std::endl; // if (... error condition detected ...) throw FullmoonException("... the problem description ..."); // Control never gets here ... std::cout << "try block - end" << std::endl; } catch(const FullmoonException& e) { std::cout << "Caught a'Fullmoon' exception. Message: " << "[" << e << "]" << std::endl; } catch(...) { std::cout << "Caught an unknown exceptione" << std::endl; } // Control gets here regardless of whether an exception is thrown or not std::cout << "main - end" << std::endl; } |
// Standard C++ sports a namespace facility which allows an application to be divided into // logical sub-systems, each of which operates within its own scope. Put very simply, the same // identifiers [i.e. name of types, objects, and functions] may be each used in a namespace // without fear of a nameclash occurring when logical sub-systems are variously combined as // an application. The name-clash problem is inherent in single-namespace languages like C; it // often occurs when several third-party libraries are used [a common occurrence in C], or // when an application scales up. The remedy is to rename identifiers, or, in the case of // functions that cannot be renamed, to wrap them up in other functions in a separate source // file. Of course the problem may be minimised via strict adherence to naming conventions. // // The C++ namespace facility is important, too, because it avoids the need to utilise certain // C language practices, in particular: // * Use of, possibly, 'clumsy' naming conventions [as described above] // * Partition an application by separating logically-related items into separate source // files. Namespaces cross file boundaries, so items may reside in several source files // and still comprise a single, logical sub-system // * Anonymous namespaces avoid use of the 'static' keyword in creating file scope globals // Global variable int age = 18; // ---- void print_age() { // Global value, 'age', is accessed std::cout << "Age is " << age << std::endl; } // ------------ int main() { // A local variable named, 'age' will act to 'shadow' the globally // defined version, thus any changes to, 'age', will not affect // the global version int age = 5; // Prints 18, the current value of the global version print_age(); // Local version is altered, *not* global version age = 23; // Prints 18, the current value of the global version print_age(); } // ---------------------------- // Global variable int age = 18; // ---- void print_age() { // Global value, 'age', is accessed std::cout << "Age is " << age << std::endl; } // ------------ int main() { // Here no local version declared: any changes affect global version age = 5; // Prints 5, the new value of the global version print_age(); // Global version again altered age = 23; // Prints 23, the new value of the global version print_age(); } // ---------------------------- // Global variable int age = 18; // ---- void print_age() { // Global value, 'age', is accessed std::cout << "Age is " << age << std::endl; } // ------------ int main() { // Global version value saved into local version int age = ::age; // Prints 18, the new value of the global version print_age(); // Global version this time altered ::age = 23; // Prints 23, the new value of the global version print_age(); // Global version value restored from saved local version ::age = age; // Prints 18, the restored value of the global version print_age(); } |
// Please refer to the corresponding section in PLEAC-C/Posix/GNU since the points raised there // about functions and function pointers apply equally to Standard C++ [briefly: functions // cannot be redefined; several same-signature functions may be called via the same function // pointer variable; code cannot be generated 'on-the-fly' (well, not without the use of // several external tools, making it an extra-language, not integral, feature)]. // @@INCOMPLETE@@ |
// Please refer to the corresponding section in PLEAC-C/Posix/GNU since all the points raised // there apply equally to Standard C++ [briefly: undefined function calls are compiler-detected // errors; function-pointer-based calls can't be checked for integrity]. // @@INCOMPLETE@@ |
// Standard C++ does not support either simple nested functions or closures, therefore the // example cannot be implemented exactly as per the Perl code /* === int outer(int arg) { int x = arg + 35; // *** wrong - illegal C++ *** int inner() { return x * 19; } return x + inner(); } === */ // The problem may, of course, be solved by defining two functions using parameter passing // where appropriate, but this is contrary to the intent of the original Perl code int inner(int x) { return x * 19; } int outer(int arg) { int x = arg + 35; return x + inner(x); } // An arguably better [but far more complicated] approach is to encapsulate all items within // a namespace, but again, is an approach that is counter the intent of the original Perl code #include <iostream> namespace nst { int x; int inner(); int outer(int arg); } // ---- int main() { std::cout << nst::outer(3) << std::endl; } // ---- int nst::inner() { return nst::x * 19; } int nst::outer(int arg) { nst::x = arg + 35; return nst::x + nst::inner(); } // Another way to solve this problem and avoiding the use of an external function, is to // create a local type and instantiate an object passing any required environment context // to the constructor. Then, what appears as a parameterless nested function call, can be // effected using 'operator()'. This approach most closely matches the original Perl code int outer(int arg) { int x = arg + 35; // 'Inner' is what is known as a Functor or Function Object [or Command Design Pattern]; it // allows objects that capture state / context to be instantiated, and that state / context // used / retained / altered at multiple future times. Both the STL and Boost Libraries // provide extensive support these constructs struct Inner { int n_; explicit Inner(int n) : n_(n) {} int operator()() const { return n_ * 19; } } inner(x); return x + inner(); } |
// @@INCOMPLETE@@ // @@INCOMPLETE@@ |