DEV Community

Cover image for 10 Most Asked Questions About C++
KiranPoudel98 for Truemark Technology

Posted on • Originally published at thedevpost.com

10 Most Asked Questions About C++

C++ is a general-purpose high-level programming language developed as an extension of C language. It is a cross-platform programming language used to create high-performance applications. And, here we have prepared a list of the most commonly asked questions about C++.

10 Most Asked Questions About C++

1. What is the “–>” operator in C+ array?

Answer:

--> is not an operator. It is in fact two separate operators, -- and >.

The conditional’s code decrements x, while returning x‘s original (not decremented) value, and then compares the original value with 0 using the > operator.

To better understand, the statement could be written as follows:

while( (x--) > 0 )
Enter fullscreen mode Exit fullscreen mode

Alternative Answer:

For something completely different… x slides to 0.

while (x --\
            \
             \
              \
               > 0)
     printf("%d ", x);
Enter fullscreen mode Exit fullscreen mode

2. What are the differences between a pointer variable and a reference variable in C++?

Answer:

A pointer can be re-assigned: int x = 5; int y = 6; int *p; p = &x; p = &y; *p = 10; assert(x == 5); assert(y == 10); . A reference cannot, and must be assigned at initialization: int x = 5; int y = 6; int &r = x;.

A pointer has its own memory address and size on the stack (4 bytes on x86), whereas a reference shares the same memory address (with the original variable) but also takes up some space on the stack. Since a reference has the same address as the original variable itself, it is safe to think of a reference as another name for the same variable.

Note: What a pointer points to can be on the stack or heap. But this doesn’t mean that a pointer must point to the stack. A pointer is just a variable that holds a memory address. This variable is on the stack. Since a reference has its own space on the stack, and since the address is the same as the variable it references. This implies that there is a real address of a reference that the compiler will not tell you. int x = 0; int &r = x; int *p = &x; int *p2 = &r; assert(p == p2);

You can have pointers to pointers to pointers offering extra levels of indirection. Whereas references only offer one level of indirection. int x = 0; int y = 0; int *p = &x; int *q = &y; int **pp = &p; pp = &q;//*pp = q **pp = 4; assert(y == 4); assert(x == 0);.

A pointer can be assigned nullptr directly, whereas reference cannot. If you try hard enough, and you know how, you can make the address of a reference nullptr. Likewise, if you try hard enough, you can have a reference to a pointer, and then that reference can contain nullptr. int *p = nullptr; int &r = nullptr; <--- compiling error int &r = *p; <--- likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0.

Pointers can iterate over an array; you can use ++ to go to the next item that a pointer is pointing to, and + 4 to go to the 5th element. This is no matter what size the object is that the pointer points to.

A pointer needs to be de-referenced with * to access the memory location it points to, whereas a reference can be used directly. A pointer to a class/struct uses -> to access it’s members whereas a reference uses a ..

References cannot be stuffed into an array, whereas pointers can be.

Const references can be bound to temporaries. Pointers cannot (not without some indirection): const int &x = int(12); //legal C++ int *y = &int(12); //illegal to dereference a temporary. This makes const& safer for use in argument lists and so forth.

3. How to iterate over the words of a string?

Answer:

For what it’s worth, here’s a way to extract tokens from an input string, relying only on standard library facilities. It’s an example of the power and elegance behind the design of the STL.

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>

int main() {
    using namespace std;
    string sentence = "And I feel fine...";
    istringstream iss(sentence);
    copy(istream_iterator<string>(iss),
         istream_iterator<string>(),
         ostream_iterator<string>(cout, "\n"));
}
Enter fullscreen mode Exit fullscreen mode

Instead of copying the extracted tokens to an output stream, one could insert them into a container, using the same generic copy algorithm.

vector<string> tokens;
copy(istream_iterator<string>(iss),
     istream_iterator<string>(),
     back_inserter(tokens));
Enter fullscreen mode Exit fullscreen mode

or create the vector directly:

vector<string> tokens{istream_iterator<string>{iss},
Enter fullscreen mode Exit fullscreen mode

Alternative Answer:

Use this to split string by a delimiter. First put the results in a pre-constructed vector, the second returns a new vector.

#include <string>
#include <sstream>
#include <vector>
#include <iterator>

template <typename Out>
void split(const std::string &s, char delim, Out result) {
    std::istringstream iss(s);
    std::string item;
    while (std::getline(iss, item, delim)) {
        *result++ = item;
    }
}

std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, std::back_inserter(elems));
    return elems;
}
Enter fullscreen mode Exit fullscreen mode

Note that this solution does not skip empty tokens, so the following will find 4 items, one of which is empty:

std::vector<std::string> x = split("one:two::three", ':');
Enter fullscreen mode Exit fullscreen mode

4. What does the explicit keyword mean?

Answer:

The compiler is allowed to make one implicit conversion to resolve the parameters to a function. What this means is that the compiler can use constructors callable with a single parameter to convert from one type to another in order to get the right type for a parameter.

Here’s an example class with a constructor that can be used for implicit conversions:

class Foo
{
public:
  // single parameter constructor, can be used as an implicit conversion
  Foo (int foo) : m_foo (foo) 
  {
  }

  int GetFoo () { return m_foo; }

private:
  int m_foo;
};
Enter fullscreen mode Exit fullscreen mode

Here’s a simple function that takes a Foo object:

void DoBar (Foo foo)
{
  int i = foo.GetFoo ();
}
Enter fullscreen mode Exit fullscreen mode

and here’s where the DoBar function is called.

int main ()
{
  DoBar (42);
}
Enter fullscreen mode Exit fullscreen mode

The argument is not a Foo object, but an int. However, there exists a constructor for Foo that takes an int so this constructor can be used to convert the parameter to the correct type.

The compiler is allowed to do this once for each parameter.

Prefixing the explicit keyword to the constructor prevents the compiler from using that constructor for implicit conversions. Adding it to the above class will create a compiler error at the function call DoBar (42). It is now necessary to call for conversion explicitly with DoBar (Foo (42))

The reason you might want to do this is to avoid accidental construction that can hide bugs. Contrived example:

  • You have a MyString(int size) class with a constructor that constructs a string of the given size. You have a function print(const MyString&), and you call print(3) (when you actually intended to call print("3")). You expect it to print “3”, but it prints an empty string of length 3 instead.

Alternative Answer:

Suppose, you have a class String:

class String {
public:
    String(int n); // allocate n bytes to the String object
    String(const char *p); // initializes object with char *p
};
Enter fullscreen mode Exit fullscreen mode

Now, if you try:

String mystring = 'x';
Enter fullscreen mode Exit fullscreen mode

The character 'x' will be implicitly converted to int and then the String(int) constructor will be called. But, this is not what the user might have intended. So, to prevent such conditions, we shall define the constructor as explicit:

class String {
public:
    explicit String (int n); //allocate n bytes
    String(const char *p); // initialize sobject with string p
};
Enter fullscreen mode Exit fullscreen mode

5. Why is “using namespace std;” considered bad practice?

Answer:

This is not related to performance at all. But consider this: you are using two libraries called Foo and Bar:

using namespace foo;
using namespace bar;
Enter fullscreen mode Exit fullscreen mode

Everything works fine, and you can call Blah() from Foo and Quux() from Bar without problems. But one day you upgrade to a new version of Foo 2.0, which now offers a function called Quux(). Now you’ve got a conflict: Both Foo 2.0 and Bar import Quux() into your global namespace. This is going to take some effort to fix, especially if the function parameters happen to match.

If you had used foo::Blah() and bar::Quux(), then the introduction of foo::Quux() would have been a non-event.

6. How do you set, clear, and toggle a single bit?

Answer:

Setting a bit

Use the bitwise OR operator (|) to set a bit.

number |= 1UL << n;
Enter fullscreen mode Exit fullscreen mode

That will set the nth bit of number. n should be zero, if you want to set the 1st bit and so on upto n-1, if you want to set the nth bit.

Use 1ULL if number is wider than unsigned long; promotion of 1UL << n doesn’t happen until after evaluating 1UL << n where it’s undefined behaviour to shift by more than the width of a long. The same applies to all the rest of the examples.

Clearing a bit

Use the bitwise AND operator (&) to clear a bit.

number &= ~(1UL << n);
Enter fullscreen mode Exit fullscreen mode

That will clear the nth bit of number. You must invert the bit string with the bitwise NOT operator (~), then AND it.

Toggling a bit

The XOR operator (^) can be used to toggle a bit.

number ^= 1UL << n;
Enter fullscreen mode Exit fullscreen mode

That will toggle the nth bit of number.

Checking a bit

To check a bit, shift the number n to the right, then bitwise AND it:

bit = (number >> n) & 1U;
Enter fullscreen mode Exit fullscreen mode

That will put the value of the nth bit of number into the variable bit.

Changing the nth bit to x

Setting the nth bit to either 1 or 0 can be achieved with the following on a 2’s complement C++ implementation:

number ^= (-x ^ number) & (1UL << n);
Enter fullscreen mode Exit fullscreen mode

Bit n will be set if x is 1, and cleared if x is 0. If x has some other value, you get garbage. x = !!x will booleanize it to 0 or 1.

To make this independent of 2’s complement negation behaviour (where -1 has all bits set, unlike on a 1’s complement or sign/magnitude C++ implementation), use unsigned negation.

number ^= (-(unsigned long)x ^ number) & (1UL << n);
Enter fullscreen mode Exit fullscreen mode

or

unsigned long newbit = !!x;    // Also booleanize to force 0 or 1
number ^= (-newbit ^ number) & (1UL << n);
Enter fullscreen mode Exit fullscreen mode

It’s generally a good idea to use unsigned types for portable bit manipulation.

or

number = (number & ~(1UL << n)) | (x << n);
Enter fullscreen mode Exit fullscreen mode

(number & ~(1UL << n)) will clear the nth bit and (x << n) will set the nth bit to x.

It’s also generally a good idea not to copy/paste code in general and so many people use preprocessor macros or some sort of encapsulation.

Alternative Answer:

Using the Standard C++ Library: std::bitset<N>.

Or the Boost version: boost::dynamic_bitset.

There is no need to roll your own:

#include <bitset>
#include <iostream>

int main()
{
    std::bitset<5> x;

    x[1] = 1;
    x[2] = 0;
    // Note x[0-4]  valid

    std::cout << x << std::endl;
}
Enter fullscreen mode Exit fullscreen mode
[Alpha:] > ./a.out
00010
Enter fullscreen mode Exit fullscreen mode

The Boost version allows a runtime sized bitset compared with a standard library compile-time sized bitset.

7. When should static_cast, dynamic_cast, const_cast, and reinterpret_cast be used?

Answer:

static_cast is the first cast you should attempt to use. It does things like implicit conversions between types (such as int to float or pointer to void*), and it can also call explicit conversion functions (or implicit ones). In many cases, explicitly stating static_cast isn’t necessary, but it’s important to note that the T(something) syntax is equivalent to (T)something and should be avoided (more on that later). A T(something, something_else) is safe, however, and guaranteed to call the constructor.

static_cast can also cast through inheritance hierarchies. It is unnecessary when casting upwards (towards a base class), but when casting downwards it can be used as long as it doesn’t cast through virtual inheritance. It does not do checking, however, and it is undefined behavior to static_cast down a hierarchy to a type that isn’t actually the type of the object.

const_cast can be used to remove or add const to a variable; no other C++ cast is capable of removing it (not even reinterpret_cast). It is important to note that modifying a formerly const value is only undefined if the original variable is const; if you use it to take the const off a reference to something that wasn’t declared with const, it is safe. This can be useful when overloading member functions based on const, for instance. It can also be used to add const to an object, such as to call a member function overload.

const_cast also works similarly on volatile, though that’s less common.

dynamic_cast is exclusively used for handling polymorphism. You can cast a pointer or reference to any polymorphic type to any other class type (a polymorphic type has at least one virtual function, declared or inherited). You can use it for more than just casting downwards – you can cast sideways or even up another chain. The dynamic_cast will seek out the desired object and return it if possible. If it can’t, it will return nullptr in the case of a pointer, or throw std::bad_cast in the case of a reference.

dynamic_cast has some limitations, though. It doesn’t work if there are multiple objects of the same type in the inheritance hierarchy (the so-called ‘dreaded diamond’) and you aren’t using virtual inheritance. It also can only go through public inheritance – it will always fail to travel through protected or private inheritance. This is rarely an issue, however, as such forms of inheritance are rare.

reinterpret_cast is the most dangerous cast, and should be used very sparingly. It turns one type directly into another — such as casting the value from one pointer to another, or storing a pointer in an int, or all sorts of other nasty things.

Largely, the only guarantee you get with reinterpret_cast is that normally if you cast the result back to the original type, you will get the exact same value (but not if the intermediate type is smaller than the original type). There are a number of conversions that reinterpret_cast cannot do, too. It’s used primarily for particularly weird conversions and bit manipulations, like turning a raw data stream into actual data or storing data in the low bits of a pointer to aligned data.

C-style cast and function-style cast are casts using (type)object or type(object), respectively, and are functionally equivalent. They are defined as the first of the following which succeeds:

  • const_cast
  • static_cast (though ignoring access restrictions)
  • static_cast (see above), then const_cast
  • reinterpret_cast
  • reinterpret_cast, then const_cast

It can therefore be used as a replacement for other casts in some instances, but can be extremely dangerous because of the ability to devolve into a reinterpret_cast, and the latter should be preferred when explicit casting is needed, unless you are sure static_cast will succeed or reinterpret_cast will fail. Even then, consider the longer, more explicit option.

C-style casts also ignore access control when performing a static_cast, which means that they have the ability to perform an operation that no other cast can. This is mostly a kludge, though, and is just another reason to avoid C-style casts.

Additional Answer:

Use dynamic_cast for converting pointers/references within an inheritance hierarchy.

Use static_cast for ordinary type conversions.

Use reinterpret_cast for low-level reinterpreting of bit patterns. Use with extreme caution.

Use const_cast for casting away const/volatile. Avoid this unless you are stuck using a const-incorrect API.

8. What is the difference between #include and #include “filename”?

Answer:

In practice, the difference is in the location where the preprocessor searches for the included file.

For #include <filename>, the preprocessor searches in an implementation-dependent manner, normally in search directories pre-designated by the compiler/IDE. This method is normally used to include standard library header files.

For #include "filename", the preprocessor searches first in the same directory as the file containing the directive and then follows the search path used for the #include <filename> form. This method is normally used to include programmer-defined header files.

9. Why is reading lines from stdin much slower in C++ than Python?

Answer:

By default, cin is synchronized with stdio, which causes it to avoid any input buffering. If you add this to the top of your main, you should see much better performance:

std::ios_base::sync_with_stdio(false);
Enter fullscreen mode Exit fullscreen mode

Normally, when an input stream is buffered, instead of reading one character at a time, the stream will be read in larger chunks. This reduces the number of system calls, which are typically relatively expensive. However, since the FILE* based stdio and iostreams often have separate implementations and therefore separate buffers, this could lead to a problem if both were used together. For example:

int myvalue1;
cin >> myvalue1;
int myvalue2;
scanf("%d",&myvalue2);
Enter fullscreen mode Exit fullscreen mode

If more input was read by cin than it actually needed, then the second integer value wouldn’t be available for the scanf function, which has its own independent buffer. This would lead to unexpected results.

To avoid this, by default, streams are synchronized with stdio. One common way to achieve this is to have cin read each character one at a time as needed using stdio functions. Unfortunately, this introduces a lot of overhead. For small amounts of input, this isn’t a big problem, but when you are reading millions of lines, the performance penalty is significant.

Fortunately, the library designers decided that you should also be able to disable this feature to get improved performance if you knew what you were doing, so they provided the sync_with_stdio method.

Alternative Answer:

You can also use dtruss/strace on each test.

C++

./a.out < in
Saw 6512403 lines in 8 seconds.  Crunch speed: 814050
Enter fullscreen mode Exit fullscreen mode

syscalls sudo dtruss -c ./a.out < in

CALL                                        COUNT
__mac_syscall                                   1
<snip>
open                                            6
pread                                           8
mprotect                                       17
mmap                                           22
stat64                                         30
read_nocancel                               25958
Enter fullscreen mode Exit fullscreen mode

Python

./a.py < in
Read 6512402 lines in 1 seconds. LPS: 6512402
Enter fullscreen mode Exit fullscreen mode

syscalls sudo dtruss -c ./a.py < in

CALL                                        COUNT
__mac_syscall                                   1
<snip>
open                                            5
pread                                           8
mprotect                                       17
mmap                                           21
stat64                                         29
Enter fullscreen mode Exit fullscreen mode

10. Why can templates only be implemented in the header file?

Answer:

It is not necessary to put the implementation in the header file, see the alternative solution at the end of this answer.

Anyway, the reason your code is failing is that, when instantiating a template, the compiler creates a new class with the given template argument. For example:

template<typename T>
struct Foo
{
    T bar;
    void doSomething(T param) {/* do stuff using T */}
};

// somewhere in a .cpp
Foo<int> f; 
Enter fullscreen mode Exit fullscreen mode

When reading this line, the compiler will create a new class (let’s call it FooInt), which is equivalent to the following:

struct FooInt
{
    int bar;
    void doSomething(int param) {/* do stuff using int */}
}
Enter fullscreen mode Exit fullscreen mode

Consequently, the compiler needs to have access to the implementation of the methods, to instantiate them with the template argument (in this case int). If these implementations were not in the header, they wouldn’t be accessible, and therefore the compiler wouldn’t be able to instantiate the template.

A common solution to this is to write the template declaration in a header file, then implement the class in an implementation file (for example .tpp), and include this implementation file at the end of the header.

Foo.h

template <typename T>
struct Foo
{
    void doSomething(T param);
};

#include "Foo.tpp"
Enter fullscreen mode Exit fullscreen mode

Foo.tpp

template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}
Enter fullscreen mode Exit fullscreen mode

This way, implementation is still separated from declaration, but is accessible to the compiler.

Alternative Answer:

Another solution is to keep the implementation separated, and explicitly instantiate all the template instances you’ll need:

Foo.h

// no implementation
template <typename T> struct Foo { ... };
Enter fullscreen mode Exit fullscreen mode

Foo.cpp

// implementation of Foo's methods

// explicit instantiations
template class Foo<int>;
template class Foo<float>;
// You will only be able to use Foo with int or float
Enter fullscreen mode Exit fullscreen mode

In Conclusion

These are the top 10 most commonly asked questions about C++. If you have any suggestions or any confusion, please comment below. If you need any help, we will be glad to help you.

We, at Truemark, provide services like web and mobile app development, digital marketing, and website development. So, if you need any help and want to work with us, please feel free to contact us.

Hope this article helped you.

This post was first published on DevPostbyTruemark.

Top comments (0)