10 questions to determine familiarity with C++



  • @MZH That kind of thing is the norm today. Working around it is one of the more common uses for a .slice() method.


  • 🚽 Regular

    @Groaner said in 10 questions to determine familiarity with C++:

    Here's one that was a real interview question put in front of me:

    class vertex {
    public:
        float x,y,z;
    
        // snip getters and setters and red herrings
    };
    
    static vertex *create()
    {
        vertex v;
        return &v;
    }
    

    "Do you see anything in this code that you would change?"

    Change that for evilness. :)



  • @MZH said in 10 questions to determine familiarity with C++:

    @lordofduct said in 10 questions to determine familiarity with C++:

    Part 1 - determine logical abilities (language independent)
    Your first few questions could be logics based. One of my favourites is:
    There is a deck of cards on one side of which are letters, and the other side of which are numbers. There is 1 rule:
    if a card has a vowel, the number on the other side must be an even number
    4 cards are dealt in random order/facing:
    E 4 5 L

    I've read that this test is only hard when presented as an abstract logic puzzle. Most people will get the following social version correct:

    There is a deck of cards on one side of which are names of drinks, and the other side of which are numbers representing the age of a person. There is 1 rule:
    If a card has an alcoholic drink, the age on the other side must be greater than or equal to 21.
    4 cards are dealt in random order/facing:
    Beer 35 17 Water

    Yes, but by giving it too them abstractly, that demonstrates they can think abstractly.

    Programming is often about abstracting concepts.



  • @MZH said in 10 questions to determine familiarity with C++:

    @lordofduct said in 10 questions to determine familiarity with C++:

    Part 1 - determine logical abilities (language independent)
    Your first few questions could be logics based. One of my favourites is:
    There is a deck of cards on one side of which are letters, and the other side of which are numbers. There is 1 rule:
    if a card has a vowel, the number on the other side must be an even number
    4 cards are dealt in random order/facing:
    E 4 5 L

    I've read that this test is only hard when presented as an abstract logic puzzle. Most people will get the following social version correct:

    There is a deck of cards on one side of which are names of drinks, and the other side of which are numbers representing the age of a person. There is 1 rule:
    If a card has an alcoholic drink, the age on the other side must be greater than or equal to 21.
    4 cards are dealt in random order/facing:
    Beer 35 17 Water

    Most people will correctly choose Beer and 17.

    Huh. Interesting. For a programming job interview, we definitely want the abstract version.

    The vertex class with evil create() method.

    I love this question, please include it.

    Given

    class A
    {
        // stuff
    };
    class B : public A
    {
        // more stuff
    };
    

    which of the following vectors can hold instances of both A and B? (Two correct answers)
    A. vector<A> v;
    B. vector<B> v;
    C. vector<A&> v;
    D. vector<A*> v;

    Of the two answers, which is the correct choice in an actual program? Why?

    This is good, especially with the second question.


  • Banned

    1. You have std::vector<int> v. Write a loop that removes all zeroes from it.

      Everyone who gets this right surely knows something about C++.

    2. What's the difference between auto, auto& and auto&& in variable type declaration?

      Tests for almost-basic C++11 knowledge.


  • Grade A Premium Asshole

    @accalia said in 10 questions to determine familiarity with C++:

    For my current job i was asked if i wrote a language i havent heard of before, i answered " Not yet, give me a book and a weekend though, and i'll give it a shot"

    They weren't asking about programming languages. That was a dig at all your typos.


  • I survived the hour long Uno hand

    @accalia said in 10 questions to determine familiarity with C++:

    i was asked if i wrote a language i havent heard of before, i answered " Not yet, give me a book and a weekend though, and i'll give it a shot"

    I once did well in an interview looking for a VB.NEt programmer and only knowing C#.Net. I told them that, but then I had to answer a big written test about VB peculiarities. I bluffed my way through it and advanced, but I ended up taking another position before the second interview.



  • @Yamikuronue said in 10 questions to determine familiarity with C++:

    @accalia said in 10 questions to determine familiarity with C++:

    i was asked if i wrote a language i havent heard of before, i answered " Not yet, give me a book and a weekend though, and i'll give it a shot"

    I once did well in an interview looking for a VB.NEt programmer and only knowing C#.Net. I told them that, but then I had to answer a big written test about VB peculiarities. I bluffed my way through it and advanced, but I ended up taking another position before the second interview.

    Lol, similar thing happened to me.

    Was in an interview for a gig and they told me it was VB, and they had a test for me to take in VB.

    I told them I know .Net well, just not VB, and if it was cool I use MSDN for it.

    Pulled up MSDN, read through the syntax real quick, blew through the test.

    They hired me because even with having to learn the language, I beat all the other applicants times by an hour (a fucking hour... which is absurd, the test was stupid).


  • area_pol

    @Kian My ideas:

    (1) Why are include guards / #pragma once used?

    (2) Solve the circular dependency problem:

    class A {
    	std::shared_ptr<B> b;
    };
    
    class B {
    	std::shared_ptr<A> a;
    };
    

    Solution: pre-declare class B; before class A.

    (3) Why does following program crash?

    class A {
    public:
    	~A() {
    		std::cout << "delete\n";
    	}
    
    	void print() {
    		print_a(std::shared_ptr<A>(this));
    	}
    
    	static void print_a(std::shared_ptr<A> a) {
    		std::cout << "print a\n";
    	}
    };
    
    int main() {
    	std::shared_ptr<A> a1 = std::make_shared<A>();
    	A::print_a(a1);
    	a1->print();
    }
    

    Creating 2 shared pointers from one heap object, but not through copying -> each of 2 shared pointers will try to delete the object.



  • @MZH said in 10 questions to determine familiarity with C++:

    Which of the following is the correct line to insert?
    A. v.size() = 1;
    B. v.capacity() = 1;
    C. v.resize(1);
    D. v.reserve(1);
    Bonus question: which incorrect answer will still compile, leading to mysterious bugs and crashes? Why?

    I like this one. Nice followup question too.

    @MZH said in 10 questions to determine familiarity with C++:

    which of the following vectors can hold instances of both A and B? (Two correct answers)

    This one is decent, but the wording might need to be reworked. Maybe "Which of the following can be used to make a collection of A and B objects". Also there's only one correct answer, the pointer one. Your other "correct" answer slices the B object, which while being well defined behavior, the behavior is not "holding a B object", but rather "extracting the A base object from the B object". So it can't hold objects of type B.

    @dse said in 10 questions to determine familiarity with C++:

    @Kian Other than 2 and 1 (aah I hate trivia questions), where is C++? Also if you ask C++ not C++11 then you are . Seriously, C++ (not C++14+) is shit.

    The test is for C++14, yes. There's a bit of legacy code lying around, but new development is in modern C++.

    @error said in 10 questions to determine familiarity with C++:

    Instances of pointers? I think we're getting at covariance vs contravariance. B "is a" A so you can put Bs in a collection of As.
    I think. Last time I used C++ was 10 years ago.
    Again, makes more sense when you give them concrete names. You can put Squares in a collection of Rectangles, obviously, but not Rectangles in a collection of Squares.

    Well, no. You can use pointers to A to point to objects of type B and call A's methods and such because "a B is an A", but when you declare an object of an actual type, you are assigning storage. Since a B can have additional data members not present in A, when you declare an A object, you only allocate storage for A's members. If you try to store a B in that A, what you get is "slicing": you will get the A subobject that exists inside B, but none of B's members. This is rarely the behavior you want. If you want a heterogeneous collection, you need to make a collection of pointers to the base class.

    @Adynathos said in 10 questions to determine familiarity with C++:

    (1) Why are include guards / #pragma once used?

    Very basic, but shows if they just do things because they always do it or if they understand some fundamental concepts. I like it.



  • @Groaner

    static vertex *create()
    {
    vertex v;
    return &v;
    }

    Having worked with C++ for a month ten years a go, I feel qualified to say this looks perfectly fine
    as long as you make sure that this call is the deepest the stack will ever go
    and you only use one vertex at a time.


  • Banned

    @Adynathos said in 10 questions to determine familiarity with C++:

    (2) Solve the circular dependency problem:

    class A {
    	std::shared_ptr<B> b;
    };
    
    class B {
    	std::shared_ptr<A> a;
    };
    

    Solution: pre-declare class B; before class A.

    This only solves half of the circular dependency problem. The easier half.



  • @MZH said in 10 questions to determine familiarity with C++:

    @ben_lubar Perhaps "instance" is the wrong word. I meant something more general like holding "data" of type A and B. Object or pointer doesn't matter. The answers I was going for were A and D, with mention of the slicing problem of answer A as the bonus.

    That explains why I was confused. I would not consider (A) a valid answer because of slicing.


  • area_pol

    @Gąska said in 10 questions to determine familiarity with C++:

    half of the circular dependency problem

    What is the other half?
    Non-pointer members like this?

    class A {
    	B b;
    };
    
    class B {
    	A a;
    };
    

    Given how non-pointer members are inserted into the class memory layout, the memory size of such a class would be infinite.


  • Banned

    @Adynathos hint: ownership.



  • @gleemonk said in 10 questions to determine familiarity with C++:

    @Groaner

    static vertex *create()
    {
    vertex v;
    return &v;
    }

    Having worked with C++ for a month ten years a go, I feel qualified to say this looks perfectly fine
    as long as you make sure that this call is the deepest the stack will ever go
    and you only use one vertex at a time.

    And you're not developing for the DeathStation 9000 compiler, which would insert a nuclear weapon detonation or nasal demons on attempt to dereference the result of vertex::create().



  • @Adynathos said in 10 questions to determine familiarity with C++:

    What is the other half?

    struct B;
    
    struct A {
      std::shared_ptr<B> b;
    };
    
    struct B {
      std::shared_ptr<A> a;
    };
    
    void f() {
      auto a = std::make_shared<A>();
      auto b = std::make_shared<B>();
      a.b = b;
      b.a = a;
    } 
    

    What happens when you leave the function? When are the A and B objects destructed?


  • ♿ (Parody)

    @lordofduct said in 10 questions to determine familiarity with C++:

    They hired me because even with having to learn the language, I beat all the other applicants times by an hour (a fucking hour... which is absurd, the test was stupid).

    FizzBuzz failures, sounds like.



  • @Kian said in 10 questions to determine familiarity with C++:

    If you want a heterogeneous collection, you need to make a collection of pointers to the base class.

    You could also use std::vector<boost::variant<Foo, Bar, Baz, Quux>> if you want to avoid being forced to use heap allocations. I use something like this for handling serialization and deserialization of different network packet types so they can all sit in the same list.


  • Winner of the 2016 Presidential Election

    @MZH said in 10 questions to determine familiarity with C++:

    vector<int> v;
    // Insert line here
    v[0] = 1;
    

    Which of the following is the correct line to insert?
    A. v.size() = 1;
    B. v.capacity() = 1;
    C. v.resize(1);
    D. v.reserve(1);

    Bonus question: which incorrect answer will still compile, leading to mysterious bugs and crashes? Why?

    Since I haven't done enough C++ to know, based on the docs it's:
    C
    D, because it only changes the capacity, not the size.
    ❓


  • Discourse touched me in a no-no place

    @gleemonk said in 10 questions to determine familiarity with C++:

    as long as you make sure that this call is the deepest the stack will ever go

    Have fun ensuring that the caller of this code never has to handle an interrupt. You sometimes can, but this is brown-alert level code.



  • @Dreikin Right.



  • @Groaner said in 10 questions to determine familiarity with C++:

    Here's one that was a real interview question put in front of me:

    static vertex *create()
    {
    vertex v;
    return &v;
    }
    

    "Do you see anything in this code that you would change?"

    isnt that returning an address of a (by the time it returns) an object that doesn't exist?

    or has c++ changed the behavior from c in that regard, like they did with changing the meaning of auto?

    [I used c++ about 9 years ago in anger, and have only kept a superficial eye on how they've been changing it since]



  • @all_users said in 10 questions to determine familiarity with C++:

    isnt that returning an address of a (by the time it returns) an object that doesn't exist?

    Correct.

    or has c++ changed the behavior from c in that regard, like they did with changing the meaning of auto?

    I consider the new meaning of auto to be quite helpful. For example:

    for(std::map<std::string, Foo::Bar::Baz::Quux>::const_iterator i = foos.begin();
    ...
    

    becomes

    for(auto i = foos.begin();
    ...
    

    This pays dividends if you ever need to change the type of foos.



  • @Groaner said in 10 questions to determine familiarity with C++:

    I consider the new meaning of auto to be quite helpful

    wasnt actually meant to come across as a dig at the change

    its not as if auto is actually useful in C

    unless your trying to confuse someone looking at your code...

    int talking_to_people_while_driving(int car, int nokia){
         auto mobile1 = car;
         auto mobile2 = nokia;
    ...
    


  • @all_users said in 10 questions to determine familiarity with C++:

    @Groaner said in 10 questions to determine familiarity with C++:

    I consider the new meaning of auto to be quite helpful

    wasnt actually meant to come across as a dig at the change

    its not as if auto is actually useful in C

    unless your trying to confuse someone looking at your code...

    int talking_to_people_while_driving(int car, int nokia){
         auto mobile1 = car;
         auto mobile2 = nokia;
    ...
    

    Well, that would be useful for the underhanded C contests...

    All in all, I like that they're reclaiming archaic keywords for more useful purposes.



  • @Groaner said in 10 questions to determine familiarity with C++:

    Well, that would be useful for the underhanded C contests...

    http://www.underhanded-c.org for those oblivious

    http://www.ioccc.org which is slightly related and I originally mistakenly thought was being talked about

    @Groaner said in 10 questions to determine familiarity with C++:

    All in all, I like that they're reclaiming archaic keywords for more useful purposes.

    have they re-purposed goto yet?


  • Discourse touched me in a no-no place

    @all_users said in 10 questions to determine familiarity with C++:

    has c++ changed the behavior from c in that regard

    Nope. You might see something interesting if create() was returning a reference, but not with a pointer. With a returned reference, you'd have some sort of copy constructor going on, etc., but with this code you'd probably not. Unless someone overrode unary operator& for vertex, but that'd be gnarly as heck and would surprise most C++ programmers a lot.

    Is it bad that I keep thinking that the function ought to be renamed to creat()? 😆



  • @dkf said in 10 questions to determine familiarity with C++:

    Is it bad that I keep thinking that the function ought to be renamed to creat()? 😆


  • kills Dumbledore

    @Groaner said in 10 questions to determine familiarity with C++:

    auto

    Is that like var in C# (type is defined at compile time but you don't have to declare it) or more like dynamic (removing all static typing, any errors come out as runtime exceptions)?


  • Discourse touched me in a no-no place

    @Jaloopa It's like var I believe; the variable gets a definite compile-time type, but you don't have to write out in full what that type is.


  • kills Dumbledore

    @dkf that's what I assumed from the context



  • @Jaloopa said in 10 questions to determine familiarity with C++:

    Is that like var in C# (type is defined at compile time but you don't have to declare it) or more like dynamic (removing all static typing, any errors come out as runtime exceptions)?

    in c, variables, unless explicitly defined otherwise, have a lifetime from the declaration until the closing brace enclosing that declaration

    so:

    int foo()
    
       static int bar;
       int baz;
    [other stuff]
    }
    

    baz gets recreated on every invocation of foo()

    bar otoh persists between invocations

    auto explicitly expresses the behaviour of baz and is redundant

    returning a reference to baz is wrong because baz doesn't actually exist after foo() returns

    bar otoh would still continue to exist because its there all the time (well at least after the first call to the function.)



  • Ok, I finished the questionnaire. Here are the questions I decided to keep. It shouldn't take more than one to three short sentences to answer each question. By the way, how do I hide things so I can post things without spoilering? Answers are at the end.

    1 - When should you use structs, and when should you use classes?

    2 - What is undefined behavior? When is it safe?

    3 - Why are include guards / #pragma once used?

    4 - Given this code:

    vector<int> v;
    // Insert line here
    v[0] = 1;
    

    Which of the following is the correct line to insert?
    a - v.size() = 1;
    b - v.capacity() = 1;
    c - v.resize(1);
    d - v.reserve(1);
    Which incorrect answer will still compile? Why?

    5 - Given a type A, and a type B which derives publicly from A, which of the following can be used to make a collection of A and B objects? Which ones will compile but do the wrong thing? What will they do?
    a - std::vector<A> v;
    b - std::vector<B> v;
    c - std::vector<A&> v;
    d - std::vector<A*> v;

    6 - When should you use std::unique_ptr parameters instead of raw pointer parameters?

    7 - What's the difference between auto, auto& and auto&&?

    8 - Given the following code:

    class vertex {
     public:
      int& getX() { return x_; }
      int& getY() { return y_; }
      int& getZ() { return z_; }
    
      void setX(int x) { x_ = x; }
      void setX(int y) { y_ = y; }
      void setX(int z) { z_ = z; }
      
      void Add(const vertex& lhs) {
        x_ += lhs.x_;
        y_ += lhs.y_;
        z_ += lhs.z_;
      }
    
      static vertex* create() {
        vertex v;
        return &v;
      }
    
     private:
      vertex() = default;
      float x_, y_, z_;
    };
    

    Would you change anything about it?

    9 - Should you allow exceptions to leave a destructor? Why, or why not?

    10 - What will calling f() print? Why?

    template <class T>
    class A {
     public:
      void Foo() {
        static_cast<T*>(this)->Bar();
      }
    };
    
    class B : public A<B> {
     public:
      void Foo() {
        std::cout << "B::Foo\n";
      }
      void Bar() {
        std::cout << "B::Bar\n";
      }
    };
    
    void f() {
      B b;
      A<B>* bPtr = &b;
      bPtr->Foo();    
    }
    

    Answers:

    1. Classes and structs are equivalent except for the default access specifier. Convention is to use structs for plain data structures and classes for objects with invariants.

    2. Undefined behavior is code that is syntactically correct, meaning it will compile, but for which the standard does not define a behavior, allowing conforming compilers to do anything. It is never safe.

    3. When the preprocessor includes a file, it will also include all the files that are included by that file. This may lead to the same header being included multiple times. This will cause the types defined in that header to be defined multiple times, leading to a "multiple definition" error. The include guards force the preprocessor to ignore the contents of a header after it's been included once.

    4. c. d will compile and under some circumstances appear to work, as it reserved space for an object that the operator[] call will use, but there is no actual object at that position yet.

    5. d can be used. a will compile but lead to slicing.

    6. You should use std::unique_ptr parameters when a function deals with ownership of an object, and raw pointers when you are handling the object itself. Bonus: You should use references instead of pointers if a function is not intended to handle a nullptr.

    7. auto will create a non-const copy of the object, regardless of constness. auto& will create a reference to an object, retaining constness if the source was const, but won't bind to rvalues unless explicitly declared const. auto&& will bind to everything and retain constness, and is mostly used to do perfect-forwarding.

    8. There are several issues that one might argue over design wise, the important bug however is the create() function returning the address of a local variable. Even if it was made to return a dynamically allocated variable, it should return a unique_ptr, not an owning raw pointer.

    9. Destructors should not leak exceptions, as during exception handling the stack will be unwinding and destructors will be called. If an exception leaks from a destructor during stack unwinding, causing two exceptions to need to be handled at the same time, the program will call std::terminate.

    10. The function prints:
      B::Bar
      Although B defines it's own Foo method, which shadows the inherited Foo method, the pointer is to the base type. The base type's Foo is a template that downcasts itself back to the derived type and calls B::Bar on itself. This is known as the curiously recurring template pattern (CRTP).


Log in to reply