Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: [C++] Periyttääkö vai eikö periyttää

vesikuusi [23.03.2013 17:28:32]

#

Alussa oli

template < class T > struct Point {
    T x;
    T y;
};

Sitten tein toisen pisteluokan FunctionalPoint, jolla on Point-luokan ilmentymä jäsenenä, ja jolle kuormittelin operaattoreita ja tein pari muuta pientä metodia (abs, toString ym.). Tämä toinen luokka on siis olevinaan "toiminnallinen piste", mutta toteutuksessa piste onkin Point-tyyppinen jäsen.

Heti alussa päätin, että FunctionalPoint ei peri Point-luokkaa. Perustelin tätä sillä, että näistä luokista tehdyillä olioilla ei ole mitään hierarkista eroa; toinen on pelkkä piste ja toinen on piste, jolla on toimintoja. Lisäksi en näe loppujen lopuksi paljonkaan merkitystä sillä, periikö FunctionalPoint Point-luokan vai onko piste jäsenenä. Tässä tulee mieleen vain polymorfismi, mutta en pidä sitä tähän "projektiin" vaikuttavana seikkana. Toisaalta merkitys voi kasvaa projektin laajentuessa.

Pyytäsinkin nyt teiltä mielipiteitä tähän ja periyttämiseen yleensä. On vielä vähän hakusessa se, milloin oikeasti pitäisi periyttää (okei Koira<-Nisäkäs<-Eläin yms. ihan selkeitä, mutta...) ja milloin pitäisi tehdä tukiluokka. Selkeämpää tämä on minulle silloin, kun näkee selvästi, mitä kuuluu luokkaan ja mitä luokan ulkopuolelle (esim. en sijoita analyyttisen geometrian kaavoja pisteluokkaan).

Projektilla ei tavoitella muuta kuin oppimista, joten luokkien hyödyllisyyteen (tai hyödyttömyyteen) ei tarvitse takertua :P

Laitan luokat tähän alle, koska salaa toivon, että saisin samalla rakentavaa palautetta koodin laadusta (älkäähän silti lynkatko). Koodista voi löytyä loogisia omituisuuksia, jotka juontavat juurensa aikaan, kun luokan rakenne oli erilainen. Näistä olen parhaani mukaan yrittänyt päästä eroon, mutta omien loogisten virheiden löytäminen onkin usein hankalaa...

// Point.hpp
#ifndef POINT_HPP
#define POINT_HPP

namespace DiGeo {
    template < class T > struct Point {
        T x;
        T y;
    };
}

#endif
// FunctionalPoint.hpp
#ifndef FunctionalPoint_HPP
#define FunctionalPoint_HPP

#include <cmath>
#include <iostream>
#include <string>

#include "Point.hpp"

namespace DiGeo {
    template < class T > class FunctionalPoint {
    /*
     * Member explanations:
     *  Point < T > m_point         | the point
     * ---------------------------------------------------------------------
     *  std::string m_asString;     | the string representation of the point
     * ---------------------------------------------------------------------
     *  std::string m_stringFormat  | the string representation format, where
     *                              | $x represents the value of m_point.x
     *                              | and $y represents the value of m_point.y.
     *                              | ($x, $y) as default.
     * ---------------------------------------------------------------------
     *
     * NB! This class expects that T is implicitly convertable
     * to float, double, long double and/or types accepted by
     * the abs-function located in cmath.
     *
     * FunctionalPoint also expects that T has overloads for operators
     *  +, -, /, *, +=, -=, /=, *=, == and that the operator =
     * is a copy constructor (standard compiler default)
     **/
    public:
        FunctionalPoint ( const Point < T >& p, const std::string& stringFormat ):
            m_point ( p ),
            m_stringFormat ( stringFormat ) {
            this->updateString ();
        }

        FunctionalPoint ( const Point < T >& p ):
            FunctionalPoint ( p, "($x, $y)" ) {}

        FunctionalPoint ( const std::string& stringFormat ):
            FunctionalPoint ( { 0, 0 }, stringFormat ) {}

        FunctionalPoint () : FunctionalPoint ( Point < T > { 0, 0 } ) {}


        FunctionalPoint < T >& stringFormat ( const std::string& format ) {
            this->m_stringFormat = format;
            this->updateString ();
            return *this;
        }

        FunctionalPoint < T >& operator = ( const Point < T >& p ) {
            this->assign ( p.x, p.y );
            return *this;
        }

        Point < T >& operator += ( const Point < T >& p ) {
            this->assign ( this->m_point.x + p.x, this->m_point.y + p.y );
            return this->m_point;
        }

        Point < T >& operator -= ( const Point < T >& p ) {
            this->assign ( this->m_point.x - p.x, this->m_point.y - p.y );
            return this->m_point;
        }

        Point < T >& operator *= ( const Point < T >& p ) {
            this->assign ( this->m_point.x * p.x, this->m_point.y * p.y );
            return this->m_point;
        }

        Point < T >& operator /= ( const Point < T >& p ) {
            this->assign ( this->m_point.x / p.x, this->m_point.y / p.y );
            return this->m_point;
        }

        Point < T >& operator ++ () {
            this->assign ( this->m_point.x + 1, this->m_point.y + 1 );
            return this->m_point;
        }

        Point < T >& operator -- () {
            this->assign ( this->m_point.x - 1, this->m_point.y - 1 );
            return this->m_point;
        }

        Point < T > operator ++ ( int ) {
            Point < T > ret ( *this );
            this->assign ( this->m_point.x + 1, this->m_point.y + 1 );
            return ret;
        }

        Point < T > operator -- ( int ) {
            Point < T > ret ( *this );
            this->assign ( this->m_point.x - 1, this->m_point.y - 1 );
            return ret;
        }

        Point < T > operator + ( const Point < T >& p ) const {
            return { this->m_point.x + p.x, this->m_point.y + p.y };
        }

        Point < T > operator - ( const Point < T >& p ) const {
            return { this->m_point.x - p.x, this->m_point.y - p.y };
        }

        Point < T > operator * (const Point < T >& p ) const {
            return { this->m_point.x * p.x, this->m_point.y * p.y };
        }

        Point < T > operator / (const Point < T >& p ) const {
            return { this->m_point.x / p.x, this->m_point.y / p.y };
        }

        bool operator == ( const Point < T >& p ) const {
            return ( this->m_point.x == p.x && m_point.y == p.y );
        }

        bool operator != ( const Point < T >& p ) const {
            return !( this->m_point.x == p.x && m_point.y == p.y );
        }

        friend std::ostream& operator << ( std::ostream& stream, const FunctionalPoint& point ) {
            stream << point.toString ();
            return stream;
        }

        operator Point < T > () const {
            return this->m_point;
        }

        operator std::string () const {
            return this->m_asString;
        }

        const Point < T >& point () const {
            return this->m_point;
        }

        const std::string stringFormat () const {
            return this->m_stringFormat;
        }

        const std::string toString () const {
            return this->m_asString;
        }

        const Point < T > abs () const {
            Point < T > absolute = this->m_point;

            absolute.x = ::abs ( absolute.x );
            absolute.y = ::abs ( absolute.y );

            return absolute;
        }

    protected:
        void updateString () {
            std::string format = this->m_stringFormat;
            std::string::size_type x, y;

            if ( ( x = format.find ( "$x" ) ) != std::string::npos ) {
                format.replace (
                    x, 2, std::to_string ( this->m_point.x )
                );
            }

            if ( ( y = format.find ( "$y" ) ) != std::string::npos ) {
                format.replace (
                    y, 2, std::to_string ( this->m_point.y )
                );
            }

            this->m_asString = format;
        }

        // All logical operator calls lead to assign
        void assign ( const T& x, const T& y ) {
            this->m_point.x = x;
            this->m_point.y = y;
            this->updateString ();
        }

        Point < T > m_point;
        std::string m_asString;
        std::string m_stringFormat;
    };
}

#endif // FunctionalPoint_HPP

Ja luokan käytöstä

#include "FunctionalPoint.hpp"
#include "PointFormulas.hpp"

template < class PType = int > class FunctionalPointTest {
private:
    DiGeo::FunctionalPoint < PType > point1;
    DiGeo::FunctionalPoint < PType > point2;

public:
    FunctionalPointTest ( const DiGeo::Point < PType >& initialValue ) {
        this->point1   = initialValue;
        this->point2  = { 0, 0 };
    }

    FunctionalPointTest () : FunctionalPointTest ( { 0, 0 } ) {}

    int main () {
        std::cout << this->point1 << std::endl;

        this->point1 =	{ 11, 4 };	// (11, 4)
        this->point1 +=	{ 10, 2 }; 	// (21, 6)
        this->point1 -=	{ 5, 1 };  	// (16, 5)
        ++this->point1;				// (17, 6)
        this->point1++;				// (18, 7)

        std::cout << this->point1 << std::endl; // (18 ,7)

        this->point1--;				// (17, 6)
        --this->point1;				// (16, 5)

        // Reformat output for point
        this->point1.stringFormat ( "MyPoint\n |x = $x\n |y = $y" );

        /*MyPoint
          |x = 16
          |y = 5*/
        std::cout << this->point1 << std::endl;

        this->point2 = ++this->point1;	// (17, 6)
        this->point2 = this->point1--;	// (17, 6)

        this->point2 /= { 2, 2 };       // ( 8, 3 )
        this->point2 *= { 3, 2 };       // ( 24, 6 )

        // Switch back to default output format for point
        this->point1.stringFormat ( "($x, $y)" );

        std::cout << this->point2 << std::endl; // (24, 6)
        std::cout << this->point1 << std::endl; // (16, 5)

        std::cout << DiGeo::FunctionalPoint < PType > (this->point1 + this->point2) << std::endl;   // (40, 11)
        std::cout << DiGeo::FunctionalPoint < PType > (this->point2 - this->point1) << std::endl;   // (8, 1)

        return EXIT_SUCCESS;
    }
};

int main () {
    FunctionalPointTest <> test ( { 10, 2 } );
    return test.main();
}

Metabolix [23.03.2013 18:08:01]

#

Kyllä tämä on ihan selvästi tilanne, jossa jäsenmuuttujalle ei ole mitään perusteita. On turhaa ylipäänsä tehdä erillinen luokka funktioille, koska funktiot eivät mitenkään suurenna olion kokoa ja toisaalta uuden olion luonti jokaista laskua varten on turha vaiva sekä koodarille että koneelle. Itse laittaisin siis kaiken Point-luokkaan ja jättäisin tuon stringFormat-jäsenen pois. Jos kuitenkin välttämättä haluat erillisen luokan, se kannattaa periyttää, koska selvästi FunctionalPoint on eräänlainen Point eikä ole mitään syytä, miksei se kelpaisi suoraan jokaiseen paikkaan, johon myös tavallinen Point kelpaa. Erillisellä luokalla sählääminen vaikuttaa hyvin vaivalloiselta ja tarpeettomalta.

Funktioitasi en jaksa erityisesti lukea, mutta sen verran täytyy sanoa, että kerto- ja jakolaskuissasi ei ole paljon järkeä; vektorin voi kertoa tai jakaa skalaarilla, ja vektoreiden välinen kertolasku voi olla pistetulo tai ristitulo.

vesikuusi [23.03.2013 18:34:15]

#

Jaa että näin. Kiitti!

Jouko Koski [23.03.2013 20:44:42]

#

Komppaan Metabolixia. Erillistä luokkaa ei ole tarpeen tehdä. Funktiot (operaattorit) ovat aika outoja; lukuja kyllä lasketaan yhteen ja kokonaislukuja on järkevää inkrementoidakin, mutta että pisteitäkin! Sikäli, kun funktioita ja operaattoreja tehdään, tekisin ne luokan ulkopuolella.

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta